VoiceClip
A voice message capsule you can play and scrub through.
Installation
pnpm add @talex-touch/tuffex
import { TxVoiceClip } from '@talex-touch/tuffex/voice-clip'
import '@talex-touch/tuffex/voice-clip/style.css'
import '@talex-touch/tuffex/base.css' // design tokens and resets, once per app
Usage
Basic
Pass the recording's URL and its known length. The capsule widens with the length, and one clip plays at a time on a page.
Loading demo...
Waveform sources and states
Pass peaks when the host already has levels, so the clip skips decoding; without src the clip is unavailable.
Loading demo...
Custom colours
Set the --tx-voice-clip-* variables on any ancestor; every clip inside reads them.
Loading demo...
Best Practices
- Pass
durationMswhen you know it: the capsule has its final width and time before the element reads the file, so nothing shifts once it loads. - Pass
peakswhen the host already has levels (the level frames of a dictation, say) to save a read and a decode. - Pass
peaksfor remote recordings: the clip only reads local ones a second time (blob:,data:,tfile:,file:). - Localise the four label props; screen readers hear the position through the slider's
aria-valuetext, and the play key's name follows its state.
API Reference
Props
| Prop | Type | Default | Description |
|---|---|---|---|
src | string | '' | The recording's URL; empty or failing to load makes the clip unavailable. |
durationMs | number | - | Known length in ms; sets the width, and the time text until the element reads the file. |
peaks | readonly number[] | - | Amplitudes in 0–1, resampled to the bars that fit; omitted, a local recording is decoded. |
disabled | boolean | false | Blocks playback and seeking; disabling a playing clip pauses it. |
playLabel | string | 'Play voice message' | Accessible name of the play key. |
pauseLabel | string | 'Pause voice message' | Accessible name of the play key while playing. |
seekLabel | string | 'Playback position' | Accessible name of the waveform slider. |
unavailableLabel | string | 'Recording unavailable' | Shown in place of the waveform while unavailable. |
Events
| Event | Payload | Description |
|---|---|---|
play | - | Playback started. |
pause | - | Playback paused, including when another clip takes over and at the end. |
ended | - | Playback reached the end; the clip then returns to the start. |
error | error: unknown | The recording could not load (MediaError) or playback was refused; an interrupted start is not an error. |
Exposed Methods
| Method | Signature | Description |
|---|---|---|
play | () => Promise<void> | Plays from the current position, or from the start once at the end. |
pause | () => void | Pauses. |
toggle | () => void | Plays or pauses. |
CSS Variables
| Variable | Default | Description |
|---|---|---|
--tx-voice-clip-bg | --tx-fill-color-light | Capsule fill. |
--tx-voice-clip-control-bg | --tx-bg-color | Play key fill. |
--tx-voice-clip-control-ink | --tx-text-color-primary | Play key glyph. |
--tx-voice-clip-accent | --tx-color-primary | The played part of the waveform. |
--tx-voice-clip-track | 28% of the primary ink | The part not yet played. |
--tx-voice-clip-ink | --tx-text-color-regular | The time and the unavailable note. |
--tx-voice-clip-focus | --tx-color-primary | Focus rings. |
- None of these is declared on the root, so setting one on the clip or on any ancestor takes effect.
Overview
- The play key, the waveform and the time form a 36px capsule; its width grows with the square root of the length, from 140px to 300px at 60 seconds and beyond, and the bar count follows the waveform's real width.
- At rest the time is the length rounded to whole seconds (at least one); while playing, or paused part-way, it counts down from that number by the whole seconds played, so starting playback never changes it; at the end the clip returns to the start and shows the full length again.
- One clip plays at a time on a page; starting one pauses whichever was playing.
- The waveform is a
role="slider": click or drag to seek, ←/→ and ↑/↓ step one second, PageUp/PageDown step 10% of the length, Home/End jump to either end; seeking never starts playback. peaksare drawn as given; otherwise a local recording is decoded, with a flat track until it lands or if it fails. An empty or failingsrcdisables the play key and replaces the waveform withunavailableLabel.- The press scale and the bar-height transition only run under
prefers-reduced-motion: no-preference; progress always updates. The clip draws left to right on every page; right-to-left pages do not mirror it.
Technologies
- Plays through a hidden
<audio preload="metadata">; while playing, a rAF loop (and the element'stimeupdatewhen frames are throttled) readscurrentTimeinto--tx-voice-clip-progress, which clips an accent-coloured copy of the waveform, so nothing re-renders per frame. - Decodes with an 8 kHz
OfflineAudioContext: the RMS of 96 buckets is stretched from the quietest tenth to the loudest bucket, averaged per bar and scaled so the tallest bar fills the height; the last 24 recordings are cached per URL. - Source:
packages/tuffex/packages/components/src/voice-clip/.
查看源码
packages/tuffex/packages/components/src/voice-clip/index.ts
Use cases
- Voice messages in a conversation: the user's recording, playable before and after it is sent.
- The raw recording beside a dictation result, to recognise it again or send it as is.
Related components
- AttachmentTray: image and file attachments above a message.
- VoiceBeam: the glow that follows the voice while recording.
- ConversationStream: the message list voice messages live in.