chromium/chrome/browser/resources/chromeos/audio/audio_player.ts

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

import {getRequiredElement} from 'chrome://resources/js/util.js';

import {AudioSample, OutputPage} from './output_page.js';
import {PageNavigator} from './page.js';

export class AudioPlayer extends HTMLElement {
  private sampleIdx: number;
  private audioDiv: HTMLDivElement;
  private audioPlay: HTMLButtonElement;
  private audioContext: AudioContext|null;
  private audioQuery: HTMLDivElement;
  private audioNameTag: HTMLParagraphElement;
  private audioExpectation: HTMLParagraphElement;
  private prevLink: HTMLButtonElement;
  private timerId: number|null;
  constructor(private audioSamples: AudioSample[]) {
    super();
    this.sampleIdx = 0;
    this.audioContext = null;
    this.timerId = null;
    const clone =
        getRequiredElement<HTMLTemplateElement>('audioPlayer-template')
                      .content.cloneNode(true);
    this.audioDiv = (clone as HTMLElement).querySelector('div')!;
    this.audioPlay = this.audioDiv.querySelector('#play-btn')!;
    this.audioQuery = this.audioDiv.querySelector('#output-qs')!;
    this.audioNameTag = this.audioDiv.querySelectorAll('p')[0]!;
    this.audioExpectation = this.audioDiv.querySelectorAll('p')[1]!;
    this.prevLink = this.audioDiv.querySelector('#back')!;
    this.prevLink.textContent = '< Back';
    this.prevLink.addEventListener('click', () => {
      this.handleBackClick();
    });

    this.setUpAudioPlayer();
    this.setButtons();
    this.appendChild(this.audioDiv);
  }

  private get current() {
    return this.audioSamples[this.sampleIdx];
  }

  setUpAudioPlayer() {
    this.audioNameTag.innerHTML = `Playing: ${this.current!.description}`;
    this.setAudioExpectation();
  }

  setAudioExpectation() {
    if (this.current!.pan === -1) {
      this.audioExpectation.innerHTML =
          'Should hear audio coming from the left channel.';
    } else if (this.current!.pan === 1) {
      this.audioExpectation.innerHTML =
          'Should hear audio coming from the right channel.';
    } else {
      this.audioExpectation.innerHTML =
          'Should hear audio of a single pitch from all channels.';
    }
  }

  setButtons() {
    const yesLink = this.audioQuery.querySelector('#output-yes')!;
    const noLink = this.audioQuery.querySelector('#output-no')!;

    yesLink.addEventListener('click', () => this.handleResponse(true));
    noLink.addEventListener('click', () => this.handleResponse(false));

    this.audioPlay.addEventListener('click', () => {
      if (this.audioContext?.state === 'running') {
        this.audioContext.suspend();
      }

      this.audioContext =
          new AudioContext({sampleRate: this.current!.sampleRate});
      const oscNode = this.audioContext.createOscillator();
      oscNode.type = 'sine';
      oscNode.channelCount = this.current!.channelCount;
      oscNode.frequency.value = this.current!.freqency;
      if (this.current!.channelCount === 2) {
        const panNode = this.audioContext.createStereoPanner();
        panNode.pan.value = this.current!.pan;
        oscNode.connect(panNode);
        panNode.connect(this.audioContext.destination);
      } else {
        oscNode.connect(this.audioContext.destination);
      }
      if (this.timerId) {
        window.clearTimeout(this.timerId);
      }
      this.timerId = window.setTimeout(() => {
        this.audioContext?.suspend();
        this.audioQuery.hidden = false;
        this.timerId = null;
      }, 3000);
      oscNode.start();
    });
  }

  createButton(buttonName: string, buttonText: string) {
    const button = document.createElement('button');
    const buttonClassName = buttonName + '-btn';
    button.setAttribute('class', buttonClassName);
    button.textContent = buttonText;
    this.audioDiv.appendChild(button);
    return button;
  }

  handleBackClick() {
    this.sampleIdx -= 1;
    this.audioContext?.suspend();
    if (this.timerId) {
      window.clearTimeout(this.timerId);
      this.timerId = null;
    }
    this.setUpAudioPlayer();
    this.prevLink.hidden = this.sampleIdx === 0;
    this.audioQuery.hidden = true;
  }

  handleResponse(response: boolean) {
    OutputPage.getInstance().setOutputMapEntry(this.current!, response);
    if (this.sampleIdx + 1 === this.audioSamples.length) {
      PageNavigator.getInstance().showPage('feedback');
    } else {
      this.sampleIdx += 1;
      this.setUpAudioPlayer();
      this.audioQuery.hidden = true;
      this.prevLink.hidden = this.sampleIdx === 0;
    }
  }
}
customElements.define('audio-player', AudioPlayer);