Repository navigation
Conversation
|
Quick notes
|
Remove the fetch/decode pipeline. `peaks` is supplied by the consumer; a missing array draws the placeholder track, which still seeks and still shows progress. Deleting the Web Audio path also removes the CORS caveat and the per-mount cost of decoding a whole file. Support stacked layouts. `variant` gains `waveform-top` and `waveform-bottom`, resolved in sass with a wrapping row and one `order` rule — the same way VVideoControls resolves `tube`, and with an identical render tree in every variant. Drop the download action. It was one button with one hardcoded behaviour; a link in the `append` slot covers it and stays a real anchor. Removes the prop, icon prop, `click:download` emit, locale key and `$download` alias. Fix the remaining untranslated strings. VAudioWaveform now labels itself so it is not an unlabelled slider when used standalone, and the playback rate suffix and remaining-time sign become `playbackRateValue` and `remainingTime` keys rather than literals. Unify the media icon aliases. `IconAliases` declares the media group (`play`, `pause`, `stop`, `skipForward`, `skipBackward`, `fullscreen`, `fullscreenExit`, `volume*`) instead of leaving it to the interface's index signature — every alias-defining iconset already supplies all of them, so this changes no behaviour but makes an omission a type error rather than a blank button. The volume button also walks the same four-step ladder as VVideoVolume instead of using two of the four volume aliases. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Thanks for the review — all five points are addressed in 620e2bd. Remove fetch — push loading peaks to the user. Different layouts. Download. Removed: prop, Missing translations. Two real gaps: Unify media icon aliases. The underlying issue is that media is the one alias group with no type-level contract: One thing under that heading you did not explicitly ask for, so please push back if you disagree: Verified on the branch: |
| loop: Boolean, | ||
| muted: Boolean, | ||
| startAt: [Number, String], | ||
| eager: Boolean, |
There was a problem hiding this comment.
This prop is not used anywhere and can be safely removed.
There was a problem hiding this comment.
Good catch — removed in 387e7d8.
It was originally read by a ref tracking whether loading had started; removing the decode pipeline left that ref dead and orphaned the prop. preload already covers loading ahead of interaction, so there was nothing left for it to do.
Also added a spec that fails when VAudio declares a prop it never reads (directly, or by name through a model composable), so a prop orphaned by a refactor can't slip through again.
`eager` was only ever read by a ref tracking whether loading had started, and removing the decode pipeline left that ref dead. `preload` already covers loading before interaction, so the prop had no implementation behind it. Add a spec that fails when VAudio declares a prop it never reads, so a prop orphaned by a refactor cannot slip through again. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Closing in favor of #23227 - I have taken it as a baseline and did some major improvements:
|
Description
Vuetify ships
VVideobut has no audio counterpart. This addsVAudioto labs: a player for self-hosted audio with a seekable waveform, built to mirror the existingVVideo/VVideoControls/VVideoVolumesplit.VAudio— the<audio>element, media state, models, peaks acquisition, error and loading statesVAudioControls— the transport row; purely presentational, no media element accessVAudioWaveform— peaks to SVG, the seek interaction, hover preview. Independently useful with nothing butpeaksand a modelZero new dependencies. Only existing Vuetify components, composables and utilities.
Peaks are the primary API, not an optimisation
The waveform can decode peaks itself (
fetch→decodeAudioData), but that needs CORS headers from the media host, and it decompresses the whole file — roughly 10 MB of memory per minute of audio. Recording URLs in real apps are usually cross-origin CDN links, so for many consumers that path simply is not available. Passing precomputedpeaksskips decoding, works cross-origin, and is SSR-safe. The docs page says this in as many words; decode remains as a same-origin convenience.Playground
packages/vuetify/dev/Playground.vueNotes for review
The iconset diff.
$stop,$download,$skipForwardand$skipBackwardexist in no iconset, and stop renders by default, so all 14 alias-defining files gain four entries — the same way$play/$pausearrived withVVideo.fa-svg.tsre-exportsfa.ts's map and inherits them, andIconAliasesis untouched, sinceplay,pause,fullscreenandvolume*are not declared there either. Every name was checked against that set's own catalogue; themdi-svgpaths come from@mdi/js(mdiStop,mdiDownload,mdiFastForward,mdiRewind). Two sets have no generic fast-forward glyph, socarbonandtableruse their interval-jump icons (forward-10,rewind-forward-10) — happy to change these.Locales.
locale/__tests__/index.spec.tsenforces structural parity, so theaudioblock is added to all 43 files. Six keys (play,pause,seek,volume,mute,unmute) are lifted verbatim from each file's ownvideoblock, so they are real translations. The six genuinely new ones (stop,download,skipForward,skipBackward,playbackRate,error) are English placeholders — I did not want to machine-translate into 42 languages. Happy to drop them toenonly if Crowdin is the preferred route.Seek semantics. A visible waveform is always the seek surface;
seek-targetdecides what happens when there is none.containertherefore takes effect together withhide-waveform, which is the compact table-row player, andnonedisables seeking everywhere. Exactly one element carries the seekrole="slider"in every combination, asserted by a spec.The
<audio>element is not exposed to assistive technology. Browsers applydisplay: nonetoaudio:not([controls])from the UA stylesheet, and an author!importantdoes not override it. Giving itcontrolswould put a duplicate set of play/seek controls in the accessibility tree and the tab order, so the component's own labelled controls are the accessible interface instead.Progress is painted from
requestAnimationFrame, not a CSS transition. A transition always animates toward its target, so the fill trailed the audio by up to onetimeupdateinterval and read as lag. The loop runs only while playing and writes a custom property directly on the element, so per-frame work never re-enters Vue's update cycle; the models still update attimeupdaterate.$audio-waveform-progress-transitionis kept as a variable, defaulting tonone, for anyone who prefers the eased behaviour.Pointer Events. The waveform uses
setPointerCapturerather than themousedown+ window-listener protocol inVSlider: one path for mouse, touch and pen, nothing to leak on the window, andpointercancelas a real cleanup signal. Flagging it since it diverges from the existing slider.Docs examples point at a third-party sample MP3, since there is no audio asset on the Vuetify CDN. Glad to swap it for a hosted one.
Checks
pnpm build vuetify— passespnpm build api— passes, no missing descriptions for the three new componentspnpm linton the touched paths — cleanvitest --project unit— full suite green (916), including the locale parity testvitest --project browser src/labs/VAudio— 20 specs green, covering click and drag seeking, keyboard, RTL, theseek-targetmatrix, the error slot and the skip-button labelsMarkup
<v-audio src="…" />is a complete player. Everything else is opt-in.