# media-time

Time display components for showing current time, duration, and remaining time in a video player

## Import

```ts
import '@videojs/html/ui/time';
import '@videojs/html/ui/time-group';
import '@videojs/html/ui/time-separator';
```

## Anatomy

```html
<media-time-group>
  <media-time type="current"></media-time>
  <media-time-separator></media-time-separator>
  <media-time type="duration"></media-time>
</media-time-group>
```

## Behavior

Three display types — `current`, `duration`, and `remaining` — in digital format with smart padding:

- **Hours** are never padded (`1:05:30`, not `01:05:30`)
- **Minutes** are padded when hours are shown (`1:05:30`, but `5:30`)
- **Seconds** are always padded (`1:05`, not `1:5`)

Hour display is triggered when either the current value or the duration exceeds 1 hour, ensuring consistency within a Group. Remaining time displays a negative sign (customizable via the `negativeSign` prop).

Use `toggle` to let current displays switch between elapsed and remaining time, or remaining and duration displays switch between those two values. The initial display comes from `type`.

Before the media reports a duration or seekable range — while metadata is still loading, for example — no time value is available. A plain display still renders the formatted zero value but sets `data-unavailable`; a toggleable display is disabled instead: it sets `data-disabled`, leaves the tab order, and ignores activation until a value arrives.

```html
<media-time toggle></media-time>
<media-time toggle type="remaining"></media-time>
<media-time toggle type="duration"></media-time>
```

## Styling

| Attribute | Values | Description |
| --- | --- | --- |
| `data-type` | `current` / `duration` / `remaining` | The type of time being displayed |
| `data-disabled` | Present / absent | Present when a toggleable display has no time value yet |
| `data-unavailable` | Present / absent | Present when a plain display has no time value yet |

The negative sign is rendered inside `<span aria-hidden="true">` and can be hidden with CSS:

```css
[data-type="remaining"] > span[aria-hidden] {
  display: none;
}
```

Dim a display while its value is unavailable:

```css
[data-disabled],
[data-unavailable] {
  opacity: 0.5;
}
```

## Accessibility

Each `<media-time>` has:

- `role="time"` and `datetime` for machine-readable time semantics
- `aria-label` for the static role label (“Current time”, “Duration”, “Remaining”)

No `aria-live` region is used — time updates too frequently and might overwhelm screen readers. The separator and negative sign are `aria-hidden="true"` because the accessible label already describes the value.

Toggleable time displays receive `role="button"`, `tabindex="0"`, and keyboard support for Enter and Space. Their `aria-label` starts with the action and includes the current value. Their `aria-description` explains which values the control toggles between.

While no time value is available, a toggleable display is disabled: it sets `aria-disabled="true"` and `tabindex="-1"`, drops the `aria-description`, changes its `aria-label` to “Media not loaded, unknown time.”, and ignores clicks and key presses. A plain display in the same state omits `datetime` and uses that same label.

## Examples

### Current Time

**index.html**

```html
<video-player class="video-player">
  <media-container>
    <video src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4" autoplay muted playsinline loop></video>
    <media-time class="media-time" type="current"></media-time>
  </media-container>
</video-player>
```

**index.css**

```css
.video-player media-container {
  position: relative;
}

.video-player media-container video {
  width: 100%;
}

.media-time {
  position: absolute;
  bottom: 10px;
  left: 10px;
  padding-block: 8px;
  padding-inline: 20px;
  font-variant-numeric: tabular-nums;
  color: black;
  background: rgba(255, 255, 255, 0.7);
  border: 1px solid rgba(255, 255, 255, 0.3);
  border-radius: 9999px;
  backdrop-filter: blur(10px);
}
```

**index.ts**

```ts
import '@videojs/html/video/player';
import '@videojs/html/ui/container';
import '@videojs/html/ui/time';
```

### Current / Duration

**index.html**

```html
<video-player class="video-player">
  <media-container>
    <video src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4" autoplay muted playsinline loop></video>
    <media-time-group class="time-group">
      <media-time type="current"></media-time>
      <media-time-separator></media-time-separator>
      <media-time type="duration"></media-time>
    </media-time-group>
  </media-container>
</video-player>
```

**index.css**

```css
.video-player media-container {
  position: relative;
}

.video-player media-container video {
  width: 100%;
}

.time-group {
  position: absolute;
  bottom: 10px;
  left: 10px;
  display: flex;
  gap: 4px;
  align-items: center;
  padding-block: 8px;
  padding-inline: 20px;
  font-variant-numeric: tabular-nums;
  color: black;
  background: rgba(255, 255, 255, 0.7);
  border: 1px solid rgba(255, 255, 255, 0.3);
  border-radius: 9999px;
  backdrop-filter: blur(10px);
}
```

**index.ts**

```ts
import '@videojs/html/video/player';
import '@videojs/html/ui/container';
import '@videojs/html/ui/time';
import '@videojs/html/ui/time-group';
import '@videojs/html/ui/time-separator';
```

### Remaining

**index.html**

```html
<video-player class="video-player">
  <media-container>
    <video src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4" autoplay muted playsinline loop></video>
    <media-time class="media-time" type="remaining"></media-time>
  </media-container>
</video-player>
```

**index.css**

```css
.video-player media-container {
  position: relative;
}

.video-player media-container video {
  width: 100%;
}

.media-time {
  position: absolute;
  bottom: 10px;
  left: 10px;
  padding-block: 8px;
  padding-inline: 20px;
  font-variant-numeric: tabular-nums;
  color: black;
  background: rgba(255, 255, 255, 0.7);
  border: 1px solid rgba(255, 255, 255, 0.3);
  border-radius: 9999px;
  backdrop-filter: blur(10px);
}
```

**index.ts**

```ts
import '@videojs/html/video/player';
import '@videojs/html/ui/container';
import '@videojs/html/ui/time';
```

### Custom Separator

**index.html**

```html
<video-player class="video-player">
  <media-container>
    <video src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4" autoplay muted playsinline loop></video>
    <media-time-group class="time-group">
      <media-time type="current"></media-time>
      <media-time-separator> of </media-time-separator>
      <media-time type="duration"></media-time>
    </media-time-group>
  </media-container>
</video-player>
```

**index.css**

```css
.video-player media-container {
  position: relative;
}

.video-player media-container video {
  width: 100%;
}

.time-group {
  position: absolute;
  bottom: 10px;
  left: 10px;
  display: flex;
  gap: 4px;
  align-items: center;
  padding-block: 8px;
  padding-inline: 20px;
  font-variant-numeric: tabular-nums;
  color: black;
  background: rgba(255, 255, 255, 0.7);
  border: 1px solid rgba(255, 255, 255, 0.3);
  border-radius: 9999px;
  backdrop-filter: blur(10px);
}
```

**index.ts**

```ts
import '@videojs/html/video/player';
import '@videojs/html/ui/container';
import '@videojs/html/ui/time';
import '@videojs/html/ui/time-group';
import '@videojs/html/ui/time-separator';
```

### Custom Negative Sign

**index.html**

```html
<video-player class="video-player">
  <media-container>
    <video src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4" autoplay muted playsinline loop></video>
    <media-time class="media-time" type="remaining" negative-sign="~"></media-time>
  </media-container>
</video-player>
```

**index.css**

```css
.video-player media-container {
  position: relative;
}

.video-player media-container video {
  width: 100%;
}

.media-time {
  position: absolute;
  bottom: 10px;
  left: 10px;
  padding-block: 8px;
  padding-inline: 20px;
  font-variant-numeric: tabular-nums;
  color: black;
  background: rgba(255, 255, 255, 0.7);
  border: 1px solid rgba(255, 255, 255, 0.3);
  border-radius: 9999px;
  backdrop-filter: blur(10px);
}
```

**index.ts**

```ts
import '@videojs/html/video/player';
import '@videojs/html/ui/container';
import '@videojs/html/ui/time';
```

## API Reference

### media-time

Displays a formatted time value (current, duration, or remaining).

#### Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `label` | `{ key: string; text: string } \| string \| ((state: TimeState) => Text \| string)` | `''` | Custom label for accessibility. |
| `negativeSign` (attribute `negative-sign`) | `string` | `'-'` | Symbol prepended to remaining time. |
| `toggle` | `boolean` | `false` | Whether the time display can be toggled. |
| `type` | `'current' \| 'duration' \| 'remaining'` | `'current'` | Which time value to display. |

#### State

State is reflected as data attributes for CSS styling.

| Property | Type | Description |
| --- | --- | --- |
| `type` | `'current' \| 'duration' \| 'remaining'` | Time display type. |
| `disabled` | `boolean` | Whether the time toggle is disabled. |
| `unavailable` | `boolean` | Whether the non-interactive time value is unavailable. |
| `seconds` | `number` | Raw value in seconds. |
| `negative` | `boolean` | Whether the time value is negative (remaining time before end). |
| `text` | `string` | Formatted display text without sign (e.g., "1:30"). |
| `phrase` | `string` | Human-readable phrase (e.g., "1 minute, 30 seconds"). |
| `datetime` | `string` | ISO 8601 duration (e.g., "PT1M30S"). |

#### Data attributes

| Attribute | Type | Description |
| --- | --- | --- |
| `data-type` | `'current' \| 'duration' \| 'remaining'` | The type of time being displayed. |
| `data-disabled` | — | Present when the time toggle is disabled. |
| `data-unavailable` | — | Present when the non-interactive time value is unavailable. |

### media-time-group

Container for composed `<media-time>` and `<media-time-separator>` displays.

### media-time-separator

Divider between time values. Hidden from screen readers.