feat(media-store): add comprehensive time formatting utilities

Add time formatting functions ported from Media Chrome:
- formatTime() - formats seconds to HH:MM:SS or MM:SS
- formatAsTimePhrase() - creates accessibility descriptions
- isValidDuration() - validates duration values
- formatDuration() - combines formatting with fallback handling

These utilities provide the foundation for time-based display components
across HTML, React, and React Native platforms.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Christian Pillsbury
2025-09-12 15:59:15 -07:00
committed by Christian Pillsbury
co-authored by Claude
parent d0d487601e
commit 7e26e547ea
2 changed files with 163 additions and 1 deletions
+3 -1
View File
@@ -7,4 +7,6 @@ export * from './component-state-definitions/play-button';
export * from './component-state-definitions/mute-button';
export * from './component-state-definitions/volume-range';
export * from './component-state-definitions/time-range';
export * from './component-state-definitions/fullscreen-button';
export * from './component-state-definitions/fullscreen-button';
export * from './component-state-definitions/duration-display';
export * from './utils/time';
+160
View File
@@ -0,0 +1,160 @@
/**
* @fileoverview Time formatting utilities for media components
*
* This module provides utilities for formatting time values in various formats
* suitable for display in media player UI components. Based on patterns from
* Media Chrome but adapted for VJS-10 architecture.
*/
/**
* Checks if a value is a valid number (not NaN, null, undefined, or Infinity)
*/
function isValidNumber(value: unknown): value is number {
return typeof value === 'number' && isFinite(value);
}
const UnitLabels = [
{
singular: 'hour',
plural: 'hours',
},
{
singular: 'minute',
plural: 'minutes',
},
{
singular: 'second',
plural: 'seconds',
},
] as const;
const toTimeUnitPhrase = (timeUnitValue: number, unitIndex: number): string => {
const unitLabel =
timeUnitValue === 1
? UnitLabels[unitIndex]?.singular
: UnitLabels[unitIndex]?.plural;
return `${timeUnitValue} ${unitLabel}`;
};
/**
* Converts numeric seconds into a human-readable phrase for accessibility
* @param seconds - A (positive or negative) time, represented as seconds
* @returns The time, represented as a phrase of hours, minutes, and seconds
*
* @example
* formatAsTimePhrase(3661) // "1 hour, 1 minute, 1 second"
* formatAsTimePhrase(90) // "1 minute, 30 seconds"
* formatAsTimePhrase(-30) // "30 seconds remaining"
*/
export function formatAsTimePhrase(seconds: number): string {
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(),
];
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".
const negativeSuffix = negative ? ' remaining' : '';
return `${timeString}${negativeSuffix}`;
}
/**
* 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
*
* @example
* formatTime(90) // "1:30"
* formatTime(3661) // "1:01:01"
* formatTime(35, 3600) // "0:35" (guided by 1-hour duration)
* formatTime(NaN) // "0:00"
* formatTime(Infinity) // "0:00"
*/
export function formatTime(seconds: number, guide?: number): string {
// Handle negative values
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 = guide ? Math.floor((guide / 60) % 60) : 0;
const gh = guide ? Math.floor(guide / 3600) : 0;
// 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
const showHours = (h as number) > 0 || gh > 0;
const hoursString = showHours ? h + ':' : '';
// If hours are showing, we may need to add a leading zero.
// Always show at least one digit of minutes.
const minutesString = ((showHours || gm >= 10) && (m as number) < 10 ? '0' + m : m) + ':';
// Check if leading zero is needed for seconds
const secondsString = (s as number) < 10 ? '0' + s : s;
return (negative ? '-' : '') + hoursString + minutesString + secondsString;
}
/**
* Checks if a duration value should be considered valid for display
* @param duration - The duration value to check
* @returns True if the duration is a valid, displayable number
*/
export function isValidDuration(duration: unknown): duration is number {
return isValidNumber(duration) && duration >= 0;
}
/**
* Formats a duration with fallback handling for invalid values
* @param duration - The duration to format in seconds
* @param guide - Optional guide duration for consistent formatting
* @param fallback - Fallback text when duration is invalid (default: "--:--")
* @returns Formatted duration string or fallback
*/
export function formatDuration(
duration: unknown,
guide?: number,
fallback: string = '--:--'
): string {
if (!isValidDuration(duration)) {
return fallback;
}
return formatTime(duration, guide);
}