chromium/ash/webui/camera_app_ui/resources/js/device/mode/record_time.ts

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

import * as dom from '../../dom.js';
import {I18nString} from '../../i18n_string.js';
import {RecordTimeChip} from '../../lit/components/record-time-chip.js';
import {getI18nMessage} from '../../models/load_time_data.js';
import {speak} from '../../spoken_msg.js';

/**
 * Time between updates in milliseconds.
 */
const UPDATE_INTERVAL_MS = 100;

/**
 * Controller for the record-time-chip of Camera view.
 */
export class RecordTime {
  private readonly recordTime = dom.get('record-time-chip', RecordTimeChip);

  /**
   * Timeout to count every tick of elapsed recording time.
   */
  private tickTimeout: number|null = null;

  /**
   * The timestamp when the recording starts.
   */
  private startTimestamp: number|null = null;

  /**
   * The total duration of the recording in milliseconds.
   */
  private totalDuration = 0;

  /**
   * Maximal recording time in milliseconds.
   */
  private maxTimeMs: number = Infinity;

  constructor(private readonly onMaxTimeout: () => void) {}

  private getTimeMessage(timeMs: number) {
    const seconds = timeMs / 1000;
    if (Number.isFinite(this.maxTimeMs)) {
      // GIF recording. Formats seconds with only first digit shown after
      // floating point.
      return getI18nMessage(
          I18nString.LABEL_CURRENT_AND_MAXIMAL_RECORD_TIME, seconds.toFixed(1),
          (this.maxTimeMs / 1000).toFixed(1));
    } else {
      // Normal recording. Format time into HH:MM:SS or MM:SS.
      const parts: number[] = [];
      if (seconds >= 3600) {
        parts.push(Math.floor(seconds / 3600));  // HH
      }
      parts.push(Math.floor(seconds / 60) % 60);  // MM
      parts.push(Math.floor(seconds % 60));       // SS
      return parts.map((n) => n.toString().padStart(2, '0')).join(':');
    }
  }

  /**
   * Starts to count and show the elapsed recording time.
   */
  start(maxTimeMs: number = Infinity): void {
    this.totalDuration = 0;
    this.maxTimeMs = maxTimeMs;
    this.resume();
  }

  /**
   * Updates UI by the elapsed recording time.
   */
  private update() {
    this.recordTime.textContent = this.getTimeMessage(this.getDuration());
  }

  /**
   * Gets the current duration of the recording.
   */
  private getDuration() {
    if (this.startTimestamp === null) {
      return this.totalDuration;
    }
    return this.totalDuration + performance.now() - this.startTimestamp;
  }

  /**
   * Resumes to count and show the elapsed recording time.
   */
  resume(): void {
    this.startTimestamp = performance.now();
    this.update();
    this.recordTime.hidden = false;

    this.tickTimeout = setInterval(() => {
      if (this.getDuration() > this.maxTimeMs) {
        this.onMaxTimeout();
        if (this.tickTimeout !== null) {
          clearInterval(this.tickTimeout);
          this.tickTimeout = null;
        }
      }
      this.update();
    }, UPDATE_INTERVAL_MS);
  }

  /**
   * Calculates total duration after recoding is stopped or paused.
   */
  private accumulateTotalDuration(): void {
    if (this.startTimestamp !== null) {
      this.totalDuration += performance.now() - this.startTimestamp;
      this.startTimestamp = null;
    }
    this.totalDuration = Math.min(this.totalDuration, this.maxTimeMs);
  }

  /**
   * Stops counting and showing the elapsed recording time.
   */
  stop(): void {
    speak(I18nString.STATUS_MSG_RECORDING_STOPPED);
    if (this.tickTimeout !== null) {
      clearInterval(this.tickTimeout);
      this.tickTimeout = null;
    }

    this.recordTime.hidden = true;
    this.accumulateTotalDuration();
  }

  /**
   * Pauses counting and showing the elapsed recording time.
   */
  pause(): void {
    speak(I18nString.STATUS_MSG_RECORDING_STOPPED);
    if (this.tickTimeout !== null) {
      clearInterval(this.tickTimeout);
      this.tickTimeout = null;
    }
    this.accumulateTotalDuration();
  }

  /**
   * Returns the recorded duration in milliseconds.
   */
  inMilliseconds(): number {
    return Math.round(this.getDuration());
  }
}