media-chrome
Version:
Custom elements (web components) for making audio and video player controls that look great in your website or app.
148 lines (129 loc) • 4.42 kB
text/typescript
import { isValidNumber } from './utils.js';
import { t } from './i18n.js';
const UnitLabels = [
{
singular: 'hour',
plural: 'hours',
},
{
singular: 'minute',
plural: 'minutes',
},
{
singular: 'second',
plural: 'seconds',
},
] as const;
const toTimeUnitPhrase = (timeUnitValue, unitIndex) => {
const unitLabel =
timeUnitValue === 1
? t(UnitLabels[unitIndex].singular)
: t(UnitLabels[unitIndex].plural);
return `${timeUnitValue} ${unitLabel}`;
};
/**
* This function converts numeric seconds into a phrase
* @param {number} seconds - a (positive or negative) time, represented as seconds
* @returns {string} The time, represented as a phrase of hours, minutes, and seconds
*/
export const formatAsTimePhrase = (seconds) => {
if (!isValidNumber(seconds)) return '';
const positiveSeconds = Math.abs(seconds);
const negative = positiveSeconds !== seconds;
const secondsDateTime = new Date(0, 0, 0, 0, 0, positiveSeconds, 0);
const timeParts = [
secondsDateTime.getHours(),
secondsDateTime.getMinutes(),
secondsDateTime.getSeconds(),
];
// NOTE: Everything above should be useable for the `formatTime` function.
const timeString = timeParts
// Convert non-0 values to a string of the value plus its unit
.map(
(timeUnitValue, index) =>
timeUnitValue && toTimeUnitPhrase(timeUnitValue, index)
)
// Ignore/exclude any 0 values
.filter((x) => x)
// join into a single comma-separated string phrase
.join(', ');
// If the time was negative, assume it represents some remaining amount of time/"count down".
if (negative) {
return t('{time} remaining', { time: timeString });
}
return timeString;
};
/**
* Converts a time, in numeric seconds, to a formatted string representation of the form [HH:[MM:]]SS, where hours and minutes
* are optional, either based on the value of `seconds` or (optionally) based on the value of `guide`.
*
* @param seconds - The total time you'd like formatted, in seconds
* @param guide - A number in seconds that represents how many units you'd want to show. This ensures consistent formatting between e.g. 35s and 4834s.
* @returns A string representation of the time, with expected units
*/
export function formatTime(seconds: number, guide?: number): string {
// Handle negative values at the end
let negative = false;
if (seconds < 0) {
negative = true;
seconds = 0 - seconds;
}
seconds = seconds < 0 ? 0 : seconds;
let s: number | string = Math.floor(seconds % 60);
let m: number | string = Math.floor((seconds / 60) % 60);
let h: number | string = Math.floor(seconds / 3600);
const gm = Math.floor((guide / 60) % 60);
const gh = Math.floor(guide / 3600);
// handle invalid times
if (isNaN(seconds) || seconds === Infinity) {
// '-' is false for all relational operators (e.g. <, >=) so this setting
// will add the minimum number of fields specified by the guide
h = m = s = '0';
}
// Check if we need to show hours
// @ts-ignore
h = h > 0 || gh > 0 ? h + ':' : '';
// If hours are showing, we may need to add a leading zero.
// Always show at least one digit of minutes.
// @ts-ignore
m = ((h || gm >= 10) && m < 10 ? '0' + m : m) + ':';
// Check if leading zero is need for seconds
// @ts-ignore
s = s < 10 ? '0' + s : s;
return (negative ? '-' : '') + h + m + s;
}
export const emptyTimeRanges: TimeRanges = Object.freeze({
length: 0,
start(index) {
const unsignedIdx = index >>> 0;
if (unsignedIdx >= this.length) {
throw new DOMException(
`Failed to execute 'start' on 'TimeRanges': The index provided (${unsignedIdx}) is greater than or equal to the maximum bound (${this.length}).`
);
}
return 0;
},
end(index) {
const unsignedIdx = index >>> 0;
if (unsignedIdx >= this.length) {
throw new DOMException(
`Failed to execute 'end' on 'TimeRanges': The index provided (${unsignedIdx}) is greater than or equal to the maximum bound (${this.length}).`
);
}
return 0;
},
});
/**
*/
export function serializeTimeRanges(
timeRanges: TimeRanges = emptyTimeRanges
): string {
return Array.from(timeRanges as any)
.map((_, i) =>
[
Number(timeRanges.start(i).toFixed(3)),
Number(timeRanges.end(i).toFixed(3)),
].join(':')
)
.join(' ');
}