1# Copyright 2024 Google LLC 2# 3# Licensed under the Apache License, Version 2.0 (the 'License'); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# https://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an 'AS IS' BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14"""This module provides cras audio utilities.""" 15 16import dbus 17import logging 18import re 19import subprocess 20 21from floss.pandora.floss import cmd_utils 22from floss.pandora.floss import utils 23 24_CRAS_TEST_CLIENT = '/usr/bin/cras_test_client' 25 26 27class CrasUtilsError(Exception): 28 """Error in CrasUtils.""" 29 pass 30 31 32def playback(blocking=True, stdin=None, *args, **kargs): 33 """A helper function to execute the playback_cmd. 34 35 Args: 36 blocking: Blocks this call until playback finishes. 37 stdin: The standard input of playback process. 38 args: Args passed to playback_cmd. 39 kargs: Kargs passed to playback_cmd. 40 41 Returns: 42 The process running the playback command. Note that if the 43 blocking parameter is true, this will return a finished process. 44 """ 45 process = cmd_utils.popen(playback_cmd(*args, **kargs), stdin=stdin) 46 if blocking: 47 cmd_utils.wait_and_check_returncode(process) 48 return process 49 50 51def capture(*args, **kargs): 52 """A helper function to execute the capture_cmd. 53 54 Args: 55 args: Args passed to capture_cmd. 56 kargs: Kargs passed to capture_cmd. 57 """ 58 cmd_utils.execute(capture_cmd(*args, **kargs)) 59 60 61def playback_cmd(playback_file, block_size=None, duration=None, pin_device=None, channels=2, rate=48000): 62 """Gets a command to playback a file with given settings. 63 64 Args: 65 playback_file: The name of the file to play. '-' indicates to playback raw audio from the stdin. 66 pin_device: The device id to playback on. 67 block_size: The number of frames per callback(dictates latency). 68 duration: Seconds to playback. 69 channels: Number of channels. 70 rate: The sampling rate. 71 72 Returns: 73 The command args put in a list of strings. 74 """ 75 args = [_CRAS_TEST_CLIENT] 76 args += ['--playback_file', playback_file] 77 if pin_device is not None: 78 args += ['--pin_device', str(pin_device)] 79 if block_size is not None: 80 args += ['--block_size', str(block_size)] 81 if duration is not None: 82 args += ['--duration', str(duration)] 83 args += ['--num_channels', str(channels)] 84 args += ['--rate', str(rate)] 85 return args 86 87 88def capture_cmd(capture_file, 89 block_size=None, 90 duration=10, 91 sample_format='S16_LE', 92 pin_device=None, 93 channels=1, 94 rate=48000): 95 """Gets a command to capture the audio into the file with given settings. 96 97 Args: 98 capture_file: The name of file the audio to be stored in. 99 block_size: The number of frames per callback(dictates latency). 100 duration: Seconds to record. If it is None, duration is not set, 101 and command will keep capturing audio until it is terminated. 102 sample_format: The sample format; possible choices: 'S16_LE', 'S24_LE', 103 and 'S32_LE' default to S16_LE: signed 16 bits/sample, little endian. 104 pin_device: The device id to record from. 105 channels: Number of channels. 106 rate: The sampling rate. 107 108 Returns: 109 The command args put in a list of strings. 110 """ 111 args = [_CRAS_TEST_CLIENT] 112 args += ['--capture_file', capture_file] 113 if pin_device is not None: 114 args += ['--pin_device', str(pin_device)] 115 if block_size is not None: 116 args += ['--block_size', str(block_size)] 117 if duration is not None: 118 args += ['--duration', str(duration)] 119 args += ['--num_channels', str(channels)] 120 args += ['--rate', str(rate)] 121 args += ['--format', str(sample_format)] 122 return args 123 124 125def listen_cmd(capture_file, block_size=None, duration=10, channels=1, rate=48000): 126 """Gets a command to listen on hotword and record audio into the file with given settings. 127 128 Args: 129 capture_file: The name of file the audio to be stored in. 130 block_size: The number of frames per callback(dictates latency). 131 duration: Seconds to record. If it is None, duration is not set, and command 132 will keep capturing audio until it is terminated. 133 channels: Number of channels. 134 rate: The sampling rate. 135 136 Returns: 137 The command args put in a list of strings. 138 """ 139 args = [_CRAS_TEST_CLIENT] 140 args += ['--listen_for_hotword', capture_file] 141 if block_size is not None: 142 args += ['--block_size', str(block_size)] 143 if duration is not None: 144 args += ['--duration', str(duration)] 145 args += ['--num_channels', str(channels)] 146 args += ['--rate', str(rate)] 147 return args 148 149 150def loopback(*args, **kargs): 151 """A helper function to execute loopback_cmd. 152 153 Args: 154 args: Args passed to loopback_cmd. 155 kargs: Kargs passed to loopback_cmd. 156 """ 157 cmd_utils.execute(loopback_cmd(*args, **kargs)) 158 159 160def loopback_cmd(output_file, duration=10, channels=2, rate=48000): 161 """Gets a command to record the loopback. 162 163 Args: 164 output_file: The name of the file the loopback to be stored in. 165 channels: The number of channels of the recorded audio. 166 duration: Seconds to record. 167 rate: The sampling rate. 168 169 Returns: 170 The command args put in a list of strings. 171 """ 172 args = [_CRAS_TEST_CLIENT] 173 args += ['--loopback_file', output_file] 174 args += ['--duration_seconds', str(duration)] 175 args += ['--num_channels', str(channels)] 176 args += ['--rate', str(rate)] 177 return args 178 179 180def get_cras_nodes_cmd(): 181 """Gets a command to query the nodes from Cras. 182 183 Returns: 184 The command to query nodes information from Cras using dbus-send. 185 """ 186 return ('dbus-send --system --type=method_call --print-reply ' 187 '--dest=org.chromium.cras /org/chromium/cras ' 188 'org.chromium.cras.Control.GetNodes') 189 190 191def _dbus_uint64(x): 192 """Returns a UINT64 python-dbus object. 193 194 *Sometimes* python-dbus fails into the following cases: 195 - Attempt to convert a 64-bit integer into int32 and overflow 196 - Convert `dbus.UInt64(12345678900, variant_level=1)` (we usually get 197 this from some DBus calls) into VARIANT rather than UINT64 198 199 This function is a helper to avoid the above flakiness. 200 """ 201 return dbus.types.UInt64(int(x), variant_level=0) 202 203 204def set_system_volume(volume): 205 """Sets the system volume. 206 207 Args: 208 volume: The system output vlume to be set(0 - 100). 209 """ 210 get_cras_control_interface().SetOutputVolume(volume) 211 212 213def set_node_volume(node_id, volume): 214 """Sets the volume of the given output node. 215 216 Args: 217 node_id: The id of the output node to be set the volume. 218 volume: The volume to be set(0-100). 219 """ 220 get_cras_control_interface().SetOutputNodeVolume(_dbus_uint64(node_id), volume) 221 222 223def get_cras_control_interface(private=False): 224 """Gets Cras DBus control interface. 225 226 Args: 227 private: Set to True to use a new instance for dbus.SystemBus instead of the shared instance. 228 229 Returns: 230 A dBus.Interface object with Cras Control interface. 231 """ 232 bus = dbus.SystemBus(private=private) 233 cras_object = bus.get_object('org.chromium.cras', '/org/chromium/cras') 234 return dbus.Interface(cras_object, 'org.chromium.cras.Control') 235 236 237def get_cras_nodes(): 238 """Gets nodes information from Cras. 239 240 Returns: 241 A dict containing information of each node. 242 """ 243 return get_cras_control_interface().GetNodes() 244 245 246def get_selected_nodes(): 247 """Gets selected output nodes and input nodes. 248 249 Returns: 250 A tuple (output_nodes, input_nodes) where each field is a list of selected 251 node IDs returned from Cras DBus API. Note that there may be multiple output/input 252 nodes being selected at the same time. 253 """ 254 output_nodes = [] 255 input_nodes = [] 256 nodes = get_cras_nodes() 257 for node in nodes: 258 if node['Active']: 259 if node['IsInput']: 260 input_nodes.append(node['Id']) 261 else: 262 output_nodes.append(node['Id']) 263 return (output_nodes, input_nodes) 264 265 266def set_selected_output_node_volume(volume): 267 """Sets the selected output node volume. 268 269 Args: 270 volume: The volume to be set (0-100). 271 """ 272 selected_output_node_ids, _ = get_selected_nodes() 273 for node_id in selected_output_node_ids: 274 set_node_volume(node_id, volume) 275 276 277def get_active_stream_count(): 278 """Gets the number of active streams. 279 280 Returns: 281 The number of active streams. 282 """ 283 return int(get_cras_control_interface().GetNumberOfActiveStreams()) 284 285 286def set_system_mute(is_mute): 287 """Sets the system mute switch. 288 289 Args: 290 is_mute: Set True to mute the system playback. 291 """ 292 get_cras_control_interface().SetOutputMute(is_mute) 293 294 295def set_capture_mute(is_mute): 296 """Sets the capture mute switch. 297 298 Args: 299 is_mute: Set True to mute the capture. 300 """ 301 get_cras_control_interface().SetInputMute(is_mute) 302 303 304def node_type_is_plugged(node_type, nodes_info): 305 """Determines if there is any node of node_type plugged. 306 307 This method is used in the AudioLoopbackDongleLabel class, where the call is 308 executed on autotest server. Use get_cras_nodes instead if the call can be executed 309 on Cros device. 310 311 Since Cras only reports the plugged node in GetNodes, we can parse the return value 312 to see if there is any node with the given type. For example, if INTERNAL_MIC is of 313 intereset, the pattern we are looking for is: 314 315 dict entry( 316 string "Type" 317 variant string "INTERNAL_MIC" 318 ) 319 320 Args: 321 node_type: A str representing node type defined in CRAS_NODE_TYPES. 322 nodes_info: A str containing output of command get_nodes_cmd. 323 324 Returns: 325 True if there is any node of node_type plugged. False otherwise. 326 """ 327 match = re.search(r'string "Type"\s+variant\s+string "%s"' % node_type, nodes_info) 328 return True if match else False 329 330 331# Cras node types reported from Cras DBus control API. 332CRAS_OUTPUT_NODE_TYPES = [ 333 'HEADPHONE', 'INTERNAL_SPEAKER', 'HDMI', 'USB', 'BLUETOOTH', 'LINEOUT', 'UNKNOWN', 'ALSA_LOOPBACK' 334] 335CRAS_INPUT_NODE_TYPES = [ 336 'MIC', 'INTERNAL_MIC', 'USB', 'BLUETOOTH', 'POST_DSP_LOOPBACK', 'POST_MIX_LOOPBACK', 'UNKNOWN', 'KEYBOARD_MIC', 337 'HOTWORD', 'FRONT_MIC', 'REAR_MIC', 'ECHO_REFERENCE' 338] 339CRAS_NODE_TYPES = CRAS_OUTPUT_NODE_TYPES + CRAS_INPUT_NODE_TYPES 340 341 342def get_filtered_node_types(callback): 343 """Returns the pair of filtered output node types and input node types. 344 345 Args: 346 callback: A callback function which takes a node as input parameter 347 and filter the node based on its return value. 348 349 Returns: 350 A tuple (output_node_types, input_node_types) where each field is 351 a list of node types defined in CRAS_NODE_TYPES, and their 'attribute_name' is True. 352 """ 353 output_node_types = [] 354 input_node_types = [] 355 nodes = get_cras_nodes() 356 for node in nodes: 357 if callback(node): 358 node_type = str(node['Type']) 359 if node_type not in CRAS_NODE_TYPES: 360 logging.warning('node type %s is not in known CRAS_NODE_TYPES', node_type) 361 if node['IsInput']: 362 input_node_types.append(node_type) 363 else: 364 output_node_types.append(node_type) 365 return (output_node_types, input_node_types) 366 367 368def get_selected_node_types(): 369 """Returns the pair of active output node types and input node types. 370 371 Returns: 372 A tuple (output_node_types, input_node_types) where each field is a list 373 of selected node types defined in CRAS_NODE_TYPES. 374 """ 375 376 def is_selected(node): 377 """Checks if a node is selected. 378 379 A node is selected if its Active attribute is True. 380 381 Returns: 382 True is a node is selected, False otherwise. 383 """ 384 return node['Active'] 385 386 return get_filtered_node_types(is_selected) 387 388 389def get_selected_output_device_name(): 390 """Returns the device name of the active output node. 391 392 Returns: 393 device name string. E.g. mtk-rt5650: :0,0. 394 """ 395 nodes = get_cras_nodes() 396 for node in nodes: 397 if node['Active'] and not node['IsInput']: 398 return node['DeviceName'] 399 return None 400 401 402def get_selected_output_device_type(): 403 """Returns the device type of the active output node. 404 405 Returns: 406 device type string. E.g. INTERNAL_SPEAKER. 407 """ 408 nodes = get_cras_nodes() 409 for node in nodes: 410 if node['Active'] and not node['IsInput']: 411 return node['Type'] 412 return None 413 414 415def set_single_selected_output_node(node_type): 416 """Sets one selected output node. 417 418 Note that Chrome UI uses SetActiveOutputNode of Cras DBus API to select one output node. 419 420 Args: 421 node_type: A node type. 422 423 Returns: 424 True if the output node type is found and set active. 425 """ 426 nodes = get_cras_nodes() 427 for node in nodes: 428 if node['IsInput']: 429 continue 430 if node['Type'] == node_type: 431 set_active_output_node(node['Id']) 432 return True 433 return False 434 435 436def set_selected_output_nodes(types): 437 """Sets selected output node types. 438 439 Note that Chrome UI uses SetActiveOutputNode of Cras DBus API to select one 440 output node. Here we use add/remove active output node to support multiple nodes. 441 442 Args: 443 types: A list of output node types. 444 """ 445 nodes = get_cras_nodes() 446 for node in nodes: 447 if node['IsInput']: 448 continue 449 if node['Type'] in types: 450 add_active_output_node(node['Id']) 451 elif node['Active']: 452 remove_active_output_node(node['Id']) 453 454 455def set_active_output_node(node_id): 456 """Sets one active output node. 457 458 Args: 459 node_id: node id. 460 """ 461 get_cras_control_interface().SetActiveOutputNode(_dbus_uint64(node_id)) 462 463 464def add_active_output_node(node_id): 465 """Adds an active output node. 466 467 Args: 468 node_id: node id. 469 """ 470 get_cras_control_interface().AddActiveOutputNode(_dbus_uint64(node_id)) 471 472 473def remove_active_output_node(node_id): 474 """Removes an active output node. 475 476 Args: 477 node_id: node id. 478 """ 479 get_cras_control_interface().RemoveActiveOutputNode(_dbus_uint64(node_id)) 480 481 482def get_node_id_from_node_type(node_type, is_input): 483 """Gets node id from node type. 484 485 Args: 486 node_type: A node type defined in CRAS_NODE_TYPES. 487 is_input: True if the node is input. False otherwise. 488 489 Returns: 490 A string for node id. 491 492 Raises: 493 CrasUtilsError: if unique node id can not be found. 494 """ 495 nodes = get_cras_nodes() 496 find_ids = [] 497 for node in nodes: 498 if node['Type'] == node_type and node['IsInput'] == is_input: 499 find_ids.append(node['Id']) 500 if len(find_ids) != 1: 501 raise CrasUtilsError('Can not find unique node id from node type %s' % node_type) 502 return find_ids[0] 503 504 505def get_device_id_of(node_id): 506 """Gets the device id of the node id. 507 508 The conversion logic is replicated from the CRAS's type definition at 509 third_party/adhd/cras/src/common/cras_types.h. 510 511 Args: 512 node_id: A string for node id. 513 514 Returns: 515 A string for device id. 516 517 Raises: 518 CrasUtilsError: if device id is invalid. 519 """ 520 device_id = str(int(node_id) >> 32) 521 if device_id == "0": 522 raise CrasUtilsError('Got invalid device_id: 0') 523 return device_id 524 525 526def get_device_id_from_node_type(node_type, is_input): 527 """Gets device id from node type. 528 529 Args: 530 node_type: A node type defined in CRAS_NODE_TYPES. 531 is_input: True if the node is input. False otherwise. 532 533 Returns: 534 A string for device id. 535 """ 536 node_id = get_node_id_from_node_type(node_type, is_input) 537 return get_device_id_of(node_id) 538 539 540def set_floss_enabled(enabled): 541 """Sets whether CRAS stack expects to use Floss. 542 543 Args: 544 enabled: True for Floss, False for Bluez. 545 """ 546 get_cras_control_interface().SetFlossEnabled(enabled) 547 548 549class CrasTestClient(object): 550 """An object to perform cras_test_client functions.""" 551 552 BLOCK_SIZE = None 553 PIN_DEVICE = None 554 SAMPLE_FORMAT = 'S16_LE' 555 DURATION = 10 556 CHANNELS = 2 557 RATE = 48000 558 559 def __init__(self): 560 self._proc = None 561 self._capturing_proc = None 562 self._playing_proc = None 563 self._capturing_msg = 'capturing audio file' 564 self._playing_msg = 'playing audio file' 565 self._wbs_cmd = '%s --set_wbs_enabled ' % _CRAS_TEST_CLIENT 566 self._enable_wbs_cmd = ('%s 1' % self._wbs_cmd).split() 567 self._disable_wbs_cmd = ('%s 0' % self._wbs_cmd).split() 568 self._info_cmd = [ 569 _CRAS_TEST_CLIENT, 570 ] 571 self._select_input_cmd = '%s --select_input ' % _CRAS_TEST_CLIENT 572 573 def start_subprocess(self, proc, proc_cmd, filename, proc_msg): 574 """Starts a capture or play subprocess 575 576 Args: 577 proc: The process. 578 proc_cmd: The process command and its arguments. 579 filename: The file name to capture or play. 580 proc_msg: The message to display in logging. 581 582 Returns: 583 True if the process is started successfully 584 """ 585 if proc is None: 586 try: 587 self._proc = subprocess.Popen(proc_cmd) 588 logging.debug('Start %s %s on the DUT', proc_msg, filename) 589 except Exception as e: 590 logging.error('Failed to popen: %s (%s)', proc_msg, e) 591 return False 592 else: 593 logging.error('cannot run the command twice: %s', proc_msg) 594 return False 595 return True 596 597 def stop_subprocess(self, proc, proc_msg): 598 """Stops a subprocess 599 600 Args: 601 proc: The process to stop. 602 proc_msg: The message to display in logging. 603 604 Returns: 605 True if the process is stopped successfully. 606 """ 607 if proc is None: 608 logging.error('cannot run stop %s before starting it.', proc_msg) 609 return False 610 611 proc.terminate() 612 try: 613 utils.poll_for_condition(condition=lambda: proc.poll() is not None, 614 exception=CrasUtilsError, 615 timeout=10, 616 sleep_interval=0.5, 617 desc='Waiting for subprocess to terminate') 618 except Exception: 619 logging.warn('Killing subprocess due to timeout') 620 proc.kill() 621 proc.wait() 622 623 logging.debug('stop %s on the DUT', proc_msg) 624 return True 625 626 def start_capturing_subprocess(self, 627 capture_file, 628 block_size=BLOCK_SIZE, 629 duration=DURATION, 630 pin_device=PIN_DEVICE, 631 sample_format=SAMPLE_FORMAT, 632 channels=CHANNELS, 633 rate=RATE): 634 """Starts capturing in a subprocess. 635 636 Args: 637 capture_file: The name of file the audio to be stored in. 638 block_size: The number of frames per callback(dictates latency). 639 duration: Seconds to record. If it is None, duration is not set, and 640 will keep capturing audio until terminated. 641 sample_format: The sample format. 642 pin_device: The device id to record from. 643 channels: Number of channels. 644 rate: The sampling rate. 645 646 Returns: 647 True if the process is started successfully. 648 """ 649 proc_cmd = capture_cmd(capture_file, 650 block_size=block_size, 651 duration=duration, 652 sample_format=sample_format, 653 pin_device=pin_device, 654 channels=channels, 655 rate=rate) 656 result = self.start_subprocess(self._capturing_proc, proc_cmd, capture_file, self._capturing_msg) 657 if result: 658 self._capturing_proc = self._proc 659 return result 660 661 def stop_capturing_subprocess(self): 662 """Stops the capturing subprocess.""" 663 result = self.stop_subprocess(self._capturing_proc, self._capturing_msg) 664 if result: 665 self._capturing_proc = None 666 return result 667 668 def start_playing_subprocess(self, 669 audio_file, 670 block_size=BLOCK_SIZE, 671 duration=DURATION, 672 pin_device=PIN_DEVICE, 673 channels=CHANNELS, 674 rate=RATE): 675 """Starts playing the audio file in a subprocess. 676 677 Args: 678 audio_file: The name of audio file to play. 679 block_size: The number of frames per callback(dictates latency). 680 duration: Seconds to play. If it is None, duration is not set, and 681 will keep playing audio until terminated. 682 pin_device: The device id to play to. 683 channels: Number of channels. 684 rate: The sampling rate. 685 686 Returns: 687 True if the process is started successfully. 688 """ 689 proc_cmd = playback_cmd(audio_file, block_size, duration, pin_device, channels, rate) 690 result = self.start_subprocess(self._playing_proc, proc_cmd, audio_file, self._playing_msg) 691 if result: 692 self._playing_proc = self._proc 693 return result 694 695 def stop_playing_subprocess(self): 696 """Stops the playing subprocess.""" 697 result = self.stop_subprocess(self._playing_proc, self._playing_msg) 698 if result: 699 self._playing_proc = None 700 return result 701 702 def play(self, 703 audio_file, 704 block_size=BLOCK_SIZE, 705 duration=DURATION, 706 pin_device=PIN_DEVICE, 707 channels=CHANNELS, 708 rate=RATE): 709 """Plays the audio file. 710 711 This method will get blocked until it has completed playing back. If you 712 do not want to get blocked, use start_playing_subprocess() above instead. 713 714 Args: 715 audio_file: The name of audio file to play. 716 block_size: The number of frames per callback(dictates latency). 717 duration: Seconds to play. If it is None, duration is not set, and 718 will keep playing audio until terminated. 719 pin_device: The device id to play to. 720 channels: Number of channels. 721 rate: The sampling rate. 722 723 Returns: 724 True if the process is started successfully. 725 """ 726 proc_cmd = playback_cmd(audio_file, block_size, duration, pin_device, channels, rate) 727 try: 728 self._proc = subprocess.call(proc_cmd) 729 logging.debug('call "%s" on the DUT', proc_cmd) 730 except Exception as e: 731 logging.error('Failed to call: %s (%s)', proc_cmd, e) 732 return False 733 return True 734 735 def enable_wbs(self, value): 736 """Enables or disable wideband speech (wbs) per the value. 737 738 Args: 739 value: True to enable wbs. 740 741 Returns: 742 True if the operation succeeds. 743 """ 744 cmd = self._enable_wbs_cmd if value else self._disable_wbs_cmd 745 logging.debug('call "%s" on the DUT', cmd) 746 if subprocess.call(cmd): 747 logging.error('Failed to call: %s (%s)', cmd) 748 return False 749 return True 750 751 def select_input_device(self, device_name): 752 """Selects the audio input device. 753 754 Args: 755 device_name: The name of the Bluetooth peer device. 756 757 Returns: 758 True if the operation succeeds. 759 """ 760 logging.debug('to select input device for device_name: %s', device_name) 761 try: 762 info = subprocess.check_output(self._info_cmd) 763 logging.debug('info: %s', info) 764 except Exception as e: 765 logging.error('Failed to call: %s (%s)', self._info_cmd, e) 766 return False 767 768 flag_input_nodes = False 769 audio_input_node = None 770 for line in info.decode().splitlines(): 771 if 'Input Nodes' in line: 772 flag_input_nodes = True 773 elif 'Attached clients' in line: 774 flag_input_nodes = False 775 776 if flag_input_nodes: 777 if device_name in line: 778 audio_input_node = line.split()[1] 779 logging.debug('%s', audio_input_node) 780 break 781 782 if audio_input_node is None: 783 logging.error('Failed to find audio input node: %s', device_name) 784 return False 785 786 select_input_cmd = (self._select_input_cmd + audio_input_node).split() 787 if subprocess.call(select_input_cmd): 788 logging.error('Failed to call: %s (%s)', select_input_cmd) 789 return False 790 791 logging.debug('call "%s" on the DUT', select_input_cmd) 792 return True 793 794 def set_player_playback_status(self, status): 795 """Sets playback status for the registered media player. 796 797 Args: 798 status: Playback status in string. 799 """ 800 try: 801 get_cras_control_interface().SetPlayerPlaybackStatus(status) 802 except Exception as e: 803 logging.error('Failed to set player playback status: %s', e) 804 return False 805 806 return True 807 808 def set_player_position(self, position): 809 """Sets media position for the registered media player. 810 811 Args: 812 position: Position in micro seconds. 813 """ 814 try: 815 get_cras_control_interface().SetPlayerPosition(position) 816 except Exception as e: 817 logging.error('Failed to set player position: %s', e) 818 return False 819 820 return True 821 822 def set_player_metadata(self, metadata): 823 """Sets title, artist, and album for the registered media player. 824 825 Args: 826 metadata: Dictionary of media metadata. 827 """ 828 try: 829 get_cras_control_interface().SetPlayerMetadata(metadata) 830 except Exception as e: 831 logging.error('Failed to set player metadata: %s', e) 832 return False 833 834 return True 835 836 def set_player_length(self, length): 837 """Sets metadata length for the registered media player. 838 839 Media length is a part of metadata information. However, without specify 840 its type to int64. dbus-python will guess the variant type to be int32 by 841 default. Separate it from the metadata function to help prepare the data 842 differently. 843 844 Args: 845 length: DBUS dictionary that contains a variant of int64. 846 """ 847 try: 848 get_cras_control_interface().SetPlayerMetadata(length) 849 except Exception as e: 850 logging.error('Failed to set player length: %s', e) 851 return False 852 return True 853