chromium/chrome/browser/resources/chromeos/accessibility/chromevox/background/event/media_automation_handler.js

// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/**
 * @fileoverview Handles media automation events.
 */
import {AsyncUtil} from '/common/async_util.js';

import {SettingsManager} from '../../common/settings_manager.js';
import {ChromeVox} from '../chromevox.js';
import {TtsCapturingEventListener} from '../tts_interface.js';

import {BaseAutomationHandler} from './base_automation_handler.js';

const AutomationEvent = chrome.automation.AutomationEvent;
const AutomationNode = chrome.automation.AutomationNode;
const EventType = chrome.automation.EventType;

/** @implements {TtsCapturingEventListener} */
export class MediaAutomationHandler extends BaseAutomationHandler {
  /** @private */
  constructor() {
    super(null);
    /** @type {!Set<AutomationNode>} @private */
    this.mediaRoots_ = new Set();

    /** @type {Date} @private */
    this.lastTtsEvent_ = new Date();
  }

  /** @private */
  async addListeners_() {
    ChromeVox.tts.addCapturingEventListener(this);

    this.node_ = await AsyncUtil.getDesktop();

    this.addListener_(
        EventType.MEDIA_STARTED_PLAYING, this.onMediaStartedPlaying);
    this.addListener_(
        EventType.MEDIA_STOPPED_PLAYING, this.onMediaStoppedPlaying);
  }

  static async init() {
    if (MediaAutomationHandler.instance) {
      throw 'Error: trying to create two instances of singleton MediaAutomationHandler';
    }
    MediaAutomationHandler.instance = new MediaAutomationHandler();
    await MediaAutomationHandler.instance.addListeners_();
  }

  /** @override */
  onTtsStart() {
    this.lastTtsEvent_ = new Date();
    this.update_({start: true});
  }

  /** @override */
  onTtsEnd() {
    const now = new Date();
    setTimeout(() => {
      const then = this.lastTtsEvent_;
      if (now < then) {
        return;
      }
      this.lastTtsEvent_ = now;
      this.update_({end: true});
    }, MediaAutomationHandler.MIN_WAITTIME_MS);
  }

  /** @override */
  onTtsInterrupted() {
    this.onTtsEnd();
  }

  /**
   * @param {!AutomationEvent} evt
   */
  onMediaStartedPlaying(evt) {
    this.mediaRoots_.add(evt.target);
    const audioStrategy = SettingsManager.get('audioStrategy');
    if (ChromeVox.tts.isSpeaking() && audioStrategy === 'audioDuck') {
      this.update_({start: true});
    }
  }

  /**
   * @param {!AutomationEvent} evt
   */
  onMediaStoppedPlaying(evt) {
    // Intentionally does nothing (to cover resume).
  }

  /**
   * Updates the media state for all observed automation roots.
   * @param {{start: (boolean|undefined),
   *          end: (boolean|undefined)}} options
   * @private
   */
  update_(options) {
    const it = this.mediaRoots_.values();
    let item = it.next();
    const audioStrategy = SettingsManager.get('audioStrategy');
    while (!item.done) {
      const root = item.value;
      if (options.start) {
        if (audioStrategy === 'audioDuck') {
          root.startDuckingMedia();
        } else if (audioStrategy === 'audioSuspend') {
          root.suspendMedia();
        }
      } else if (options.end) {
        if (audioStrategy === 'audioDuck') {
          root.stopDuckingMedia();
        } else if (audioStrategy === 'audioSuspend') {
          root.resumeMedia();
        }
      }
      item = it.next();
    }
  }
}

/** @const {number} */
MediaAutomationHandler.MIN_WAITTIME_MS = 1000;

/** @type {MediaAutomationHandler} */
MediaAutomationHandler.instance;