Skip to content

Commit

Permalink
CCA: Migrate record time to lit
Browse files Browse the repository at this point in the history
Bug: b:230703573
Test: manually
Test: camera.CCAUISmoke* camera.CCAUIStress*
Change-Id: Ic68a3cb3d115410abc80e1f0e59dff2bed95557a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4975283
Reviewed-by: Chu-Hsuan Yang <chuhsuan@chromium.org>
Commit-Queue: Pi-Hsun Shih <pihsun@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1215387}
  • Loading branch information
peter50216 authored and Chromium LUCI CQ committed Oct 26, 2023
1 parent 7834d77 commit 4d2edd4
Show file tree
Hide file tree
Showing 11 changed files with 179 additions and 159 deletions.
56 changes: 7 additions & 49 deletions ash/webui/camera_app_ui/resources/css/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
--fixed-width-font-family: 'Cousine', monospace;
--left-line: 44px;
--right-line: 50px;
--top-line: 48px;

/* From https://www.w3.org/TR/css-backgrounds-3/#corner-overlap. */
--border-radius-rounded-with-short-side: 9999999px;
Expand Down Expand Up @@ -129,7 +130,7 @@ input[type=radio].icon {

.top-stripe {
position: absolute;
top: 48px;
top: var(--top-line);
transform: translateY(-50%);
}

Expand Down Expand Up @@ -161,12 +162,6 @@ body.taking.video #options-group :not(#open-ptz-panel) {
transform: translateX(50%);
}

.horizontal-center-stripe {
left: 50%;
position: absolute;
transform: translateX(-50%);
}

.vertical-center-stripe {
position: absolute;
top: 50%;
Expand All @@ -181,10 +176,6 @@ body.taking.video #options-group :not(#open-ptz-panel) {
transform: translate(-50%, -50%);
}

.top-stripe.horizontal-center-stripe {
transform: translate(-50%, -50%);
}

.bottom-stripe.right-stripe {
transform: translate(50%, 50%);
}
Expand Down Expand Up @@ -1042,18 +1033,12 @@ body.grid.grid-4x4 #preview-grid-vertical::before {
transition: width 500ms;
}

#record-time {
align-items: center;
background-color: var(--cros-sys-base_elevated);
border-radius: var(--border-radius-rounded-with-short-side);
box-sizing: border-box;
display: flex;
font: var(--cros-button-2-font);
font-variant-numeric: tabular-nums;
height: 32px;
justify-content: flex-start;
padding: 6px 12px;
record-time-chip {
left: 50%;
pointer-events: none;
position: absolute;
top: var(--top-line);
transform: translate(-50%, -50%);
}

/*
Expand All @@ -1068,33 +1053,6 @@ body.grid.grid-4x4 #preview-grid-vertical::before {
display: none !important;
}

#record-time .icon {
background-color: var(--cros-ref-error40);
border-radius: 50%;
flex-shrink: 0;
height: 6px;
width: 6px;
}

body.recording-ui-paused #record-time .icon,
body:not(.recording-ui-paused) #record-time [i18n-text=record_video_paused_msg] {
display: none;
}

#record-time #record-time-msg {
color: var(--cros-sys-on_surface);
flex-shrink: 0;
}

#record-time [i18n-text=record_video_paused_msg] {
color: var(--cros-sys-on_surface);
flex-shrink: 0;
}

#record-time #record-time-msg {
margin-inline-start: 8px;
}

#spoken_msg {
opacity: 0;
}
Expand Down
2 changes: 1 addition & 1 deletion ash/webui/camera_app_ui/resources/js/custom_effect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import * as animation from './animation.js';
import {assertEnumVariant, assertExists, assertNotReached} from './assert.js';
import * as dom from './dom.js';
import {I18nString} from './i18n_string.js';
import {SvgWrapper} from './lit/svg-wrapper.js';
import {SvgWrapper} from './lit/components/svg-wrapper.js';
import * as loadTimeData from './models/load_time_data.js';
import * as state from './state.js';
import {PerfEvent} from './type.js';
Expand Down
130 changes: 44 additions & 86 deletions ash/webui/camera_app_ui/resources/js/device/mode/record_time.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,20 @@

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

/**
* Maximal recording time in milliseconds and the function executed to notify
* caller the tick reaches maximal time.
* Time between updates in milliseconds.
*/
export interface MaxTimeOption {
maxTime: number;
onMaxTimeout: () => void;
}
const UPDATE_INTERVAL_MS = 100;

/**
* Controller for the record-time of Camera view.
* Controller for the record-time-chip of Camera view.
*/
abstract class RecordTimeBase {
private readonly recordTime = dom.get('#record-time', HTMLElement);

protected readonly maxTimeOption: MaxTimeOption|null = null;
export class RecordTime {
private readonly recordTime = dom.get('record-time-chip', RecordTimeChip);

/**
* Timeout to count every tick of elapsed recording time.
Expand All @@ -45,34 +40,50 @@ abstract class RecordTimeBase {
private totalDuration = 0;

/**
* @return Time interval to update ticks in milliseconds.
* Maximal recording time in milliseconds.
*/
protected abstract getTimeInterval(): number;
private maxTimeMs: number|null = null;

/**
* @param ticks Aggregated time ticks during the record time.
* @return Message showing on record time area. Should already be translated
* by i18n if necessary.
*/
protected abstract getTimeMessage(ticks: number): string;
constructor(private readonly onMaxTimeout: () => void) {}

/**
* Updates UI by the elapsed recording time.
*/
private update() {
dom.get('#record-time-msg', HTMLElement).textContent =
this.getTimeMessage(this.ticks);
private getTimeMessage(timeMs: number, maxTimeMs: number|null) {
const seconds = timeMs / 1000;
if (maxTimeMs === null) {
// 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(':');
} else {
// GIF recording. Formats seconds with only first digit shown after
// floating point.
return getI18nMessage(
I18nString.LABEL_CURRENT_AND_MAXIMAL_RECORD_TIME, seconds.toFixed(1),
(maxTimeMs / 1000).toFixed(1));
}
}

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

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

/**
* Resumes to count and show the elapsed recording time.
*/
Expand All @@ -81,19 +92,18 @@ abstract class RecordTimeBase {
this.recordTime.hidden = false;

this.tickTimeout = setInterval(() => {
if (this.maxTimeOption === null ||
(this.ticks + 1) * this.getTimeInterval() <=
this.maxTimeOption.maxTime) {
if (this.maxTimeMs === null ||
(this.ticks + 1) * UPDATE_INTERVAL_MS <= this.maxTimeMs) {
this.ticks++;
} else {
this.maxTimeOption.onMaxTimeout();
this.onMaxTimeout();
if (this.tickTimeout !== null) {
clearInterval(this.tickTimeout);
this.tickTimeout = null;
}
}
this.update();
}, this.getTimeInterval());
}, UPDATE_INTERVAL_MS);

this.startTimestamp = performance.now();
}
Expand All @@ -103,9 +113,8 @@ abstract class RecordTimeBase {
*/
private calculateDuration(): void {
this.totalDuration += performance.now() - this.startTimestamp;
if (this.maxTimeOption !== null) {
this.totalDuration =
Math.min(this.totalDuration, this.maxTimeOption.maxTime);
if (this.maxTimeMs !== null) {
this.totalDuration = Math.min(this.totalDuration, this.maxTimeMs);
}
}

Expand Down Expand Up @@ -146,54 +155,3 @@ abstract class RecordTimeBase {
return Math.round(this.totalDuration);
}
}

/**
* Record time for normal record type.
*/
export class RecordTime extends RecordTimeBase {
getTimeInterval(): number {
return 1000;
}

getTimeMessage(ticks: number): string {
// Format time into HH:MM:SS or MM:SS.
function pad(n: number) {
return (n < 10 ? '0' : '') + n;
}
let hh = '';
if (ticks >= 3600) {
hh = pad(Math.floor(ticks / 3600)) + ':';
}
const mm = pad(Math.floor(ticks / 60) % 60) + ':';
return hh + mm + pad(ticks % 60);
}
}

/**
* Record time for gif record type.
*/
export class GifRecordTime extends RecordTimeBase {
declare protected readonly maxTimeOption: MaxTimeOption;

constructor(maxTimeOption: MaxTimeOption) {
super();
this.maxTimeOption = maxTimeOption;
}

getTimeInterval(): number {
return 100;
}

getTimeMessage(ticks: number): string {
const maxTicks = this.maxTimeOption.maxTime / this.getTimeInterval();
/**
* Formats ticks to seconds with only first digit shown after floating
* point.
*/
const formatTick = (ticks: number): string =>
(ticks / (1000 / this.getTimeInterval())).toFixed(1);
return loadTimeData.getI18nMessage(
I18nString.LABEL_CURRENT_AND_MAXIMAL_RECORD_TIME, formatTick(ticks),
formatTick(maxTicks));
}
}
18 changes: 5 additions & 13 deletions ash/webui/camera_app_ui/resources/js/device/mode/video.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ import {StreamManagerChrome} from '../stream_manager_chrome.js';

import {ModeBase, ModeFactory} from './mode_base.js';
import {PhotoResult} from './photo.js';
import {GifRecordTime, RecordTime} from './record_time.js';
import {RecordTime} from './record_time.js';

/**
* Maps from board name to its default encoding profile and bitrate multiplier.
Expand Down Expand Up @@ -240,12 +240,7 @@ export class Video extends ModeBase {
/**
* Record-time for the elapsed recording time.
*/
private readonly recordTime = new RecordTime();

/**
* Record-time for the elapsed gif recording time.
*/
private readonly gifRecordTime: GifRecordTime;
private readonly recordTime = new RecordTime(() => this.stop());

/**
* Record type of ongoing recording.
Expand Down Expand Up @@ -295,9 +290,6 @@ export class Video extends ModeBase {
const {width, height} = video.getVideoSettings();
return new Resolution(width, height);
})();

this.gifRecordTime = new GifRecordTime(
{maxTime: MAX_GIF_DURATION_MS, onMaxTimeout: () => this.stop()});
}

override async clear(): Promise<void> {
Expand Down Expand Up @@ -602,7 +594,7 @@ export class Video extends ModeBase {
this.recordingType === RecordType.GIF);
if (this.recordingType === RecordType.GIF) {
state.set(state.State.RECORDING, true);
this.gifRecordTime.start();
this.recordTime.start(MAX_GIF_DURATION_MS);

let gifSaver = null;
try {
Expand All @@ -615,7 +607,7 @@ export class Video extends ModeBase {
throw e;
} finally {
state.set(state.State.RECORDING, false);
this.gifRecordTime.stop();
this.recordTime.stop();
}

const gifName = (new Filenamer()).newVideoName(VideoType.GIF);
Expand All @@ -625,7 +617,7 @@ export class Video extends ModeBase {
name: gifName,
gifSaver,
resolution: this.captureResolution,
duration: this.gifRecordTime.inMilliseconds(),
duration: this.recordTime.inMilliseconds(),
})];
} else if (this.recordingType === RecordType.TIME_LAPSE) {
// TODO(b/279865370): Don't pause when the confirm dialog is shown.
Expand Down
4 changes: 3 additions & 1 deletion ash/webui/camera_app_ui/resources/js/js.gni
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ compile_js_files = [
"lib/comlink.ts",
"lib/comlink_protocol.ts",
"lib/ffmpeg.js",
"lit/svg-wrapper.ts",
"lit/components/record-time-chip.ts",
"lit/components/svg-wrapper.ts",
"lit/state_observer_controller.ts",
"local_dev.ts",
"main.ts",
"memory_usage.ts",
Expand Down

0 comments on commit 4d2edd4

Please sign in to comment.