Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions packages/api-generator/src/locale/en/VAudio.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
{
"description": "A player for self-hosted audio files, with a seekable waveform and a configurable transport bar.",
"props": {
"src": "Media file URL",
"srcObject": "Sets the source to a MediaStream, MediaSource, or Blob object. Live streams report an infinite duration, so the time display shows elapsed only and the waveform falls back to a placeholder track.",
"type": "Media file type (optional). When set, the source is rendered as a nested `<source>` element so the hint is honoured.",
"crossorigin": "Forwarded to the native audio element. Needed only when the element itself is routed into a Web Audio graph by the consumer.",
"preload": "Forwarded to the native audio element. The default `metadata` obtains the duration without downloading the whole file.",
"currentTime": "Playback position in seconds. Writing to it seeks; it stays in sync with `progress`, which expresses the same position as a percentage.",
"error": "Puts the component in the manual error state.",
"autoplay": "Starts loading the media file without waiting for the user to click. Playback begins once enough data is loaded.",
"loop": "Restarts playback when the end is reached. The `ended` event is not emitted while looping.",
"muted": "Hides the volume control and disables the playback sound.",
"startAt": "Moves progress to the specified time (in seconds) once the media file is loaded.",
"disabled": "Removes all interaction and dims the component.",
"seekTarget": "Which surface responds to a click when no waveform is shown. A visible waveform is always the seek surface, so `container` takes effect together with `hide-waveform` — that combination makes the whole component click-to-seek. `none` disables seeking entirely.",
"rounded": "Applies a border radius to the container and the controls. Accepts an array of two values to customize the elements separately.",
"controlsProps": "Pass props through to the `v-audio-controls` component. Accepts an object with anything from [v-audio-controls](/api/v-audio-controls/#props) props, camelCase keys are recommended.",
"peaks": "Waveform amplitudes, nominally 0-1 and one per bar. Generate them where the file is produced; without them a flat placeholder track is drawn."
},
"events": {
"loaded": "Emitted when the audio has loaded its metadata and is ready to be played.",
"ended": "Emitted when playback reaches the end. Not emitted while `loop` is set.",
"error": "Emits the native MediaError when the audio fails to load, or `false` when the user triggers a retry.",
"update:playing": "Emitted when the playing state changes.",
"update:progress": "Emitted when the playback position changes, as a percentage (0-100).",
"update:currentTime": "Emitted when the playback position changes, in seconds.",
"update:volume": "Emitted when the volume changes.",
"update:playbackRate": "Emitted when the playback rate changes.",
"update:error": "Emitted when the error state changes."
},
"slots": {
"controls": "Replaces the control bar with custom content.",
"prepend": "Additional content on the left within the control bar.",
"append": "Additional content on the right within the control bar.",
"waveform": "Replaces the waveform between the controls and the time display.",
"time": "Replaces the time display. Receives the elapsed, remaining and total time already formatted.",
"loader": "Slot used to replace the loader displayed while the audio is being loaded.",
"error": "Slot used to replace the default error state. Receives the MediaError and a `retry` function.",
"sources": "Slot for content placed within the native `<audio>` element."
},
"exposed": {
"audio": "The underlying native audio element.",
"play": "Start playback.",
"pause": "Pause playback.",
"stop": "Pause playback and return to the start.",
"skipTo": "Skip to a specific point in the playback. Accepts a percent value (0-100).",
"skipBy": "Move the playback position by a number of seconds, positive or negative.",
"retry": "Clear the error state and load the source again.",
"toggleMuted": "Mute/unmute audio."
}
}
53 changes: 53 additions & 0 deletions packages/api-generator/src/locale/en/VAudioControls.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"description": "Subcomponent for `v-audio` used to display the transport bar.",
"props": {
"playing": "Applies the correct icon to the default play button.",
"progress": "Controls the playback position (0 ~ 100).",
"duration": "Total duration of the audio, used to calculate the displayed time.",
"volume": "Volume value passed to the underlying control and slots.",
"playbackRate": "Current playback rate. Only meaningful together with `playbackRates`.",
"playbackRates": "Rates offered by the playback speed menu. Leaving it empty hides the menu.",
"variant": "Layout of the control bar. `waveform-top` and `waveform-bottom` move the waveform onto its own row above or below the actions; `mini` renders play and time only; `hidden` renders nothing.",
"hidePlay": "Hides the default play button.",
"hideStop": "Hides the default stop button.",
"hideVolume": "Hides the default volume control.",
"hideTime": "Hides the time display.",
"hideWaveform": "Hides the waveform. With `seek-target=\"container\"` this turns the whole component into the seek surface.",
"skipInterval": "Seconds moved by the skip buttons. `0` hides them. The value is announced in the buttons' labels.",
"splitTime": "Splits time into elapsed and remaining on each side of the waveform.",
"timeDisplay": "Format of the single time display. Ignored when `split-time` is set.",
"playIcon": "Icon used for the play action.",
"pauseIcon": "Icon used for the pause action.",
"stopIcon": "Icon used for the stop action.",
"volumeIcon": "Icon used when the volume is above zero. Unset, the icon steps through the volume aliases as the level changes.",
"muteIcon": "Icon used when audio is muted.",
"skipForwardIcon": "Icon used for the skip forward action.",
"skipBackwardIcon": "Icon used for the skip backward action.",
"color": "General color applied to icons, the played waveform and sliders.",
"backgroundColor": "Container background color.",
"pills": "Makes the container transparent and shows inner actions in separated boxes.",
"detached": "Moves the container below so it reads as a separate surface.",
"floating": "Introduces visual spacing from the container boundaries.",
"waveformProps": "Pass props through to the `v-audio-waveform` component. Accepts an object with anything from [v-audio-waveform](/api/v-audio-waveform/#props) props, camelCase keys are recommended."
},
"events": {
"update:playing": "Emitted when the playing state changes.",
"update:progress": "Emitted when the playback position changes.",
"update:volume": "Emitted when the volume value changes.",
"update:playbackRate": "Emitted when the playback rate changes.",
"skip": "Emitted when a skip action is triggered. Carries the target position as a percentage.",
"scrubStart": "Emitted when the user starts dragging the waveform.",
"scrubEnd": "Emitted when the user stops dragging the waveform.",
"click:stop": "Emitted when the stop button is clicked."
},
"slots": {
"default": "Replaces the entire contents of the control bar.",
"prepend": "Additional content on the left within the control bar.",
"append": "Additional content on the right within the control bar.",
"waveform": "Replaces the waveform between the controls and the time display.",
"time": "Replaces the time display. Receives the elapsed, remaining and total time already formatted."
},
"exposed": {
"toggleMuted": "Toggles the muted state of the audio."
}
}
32 changes: 32 additions & 0 deletions packages/api-generator/src/locale/en/VAudioWaveform.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"description": "Subcomponent for `v-audio` that draws an amplitude waveform and acts as the seek surface. Usable on its own with nothing but `peaks` and a model.",
"props": {
"modelValue": "Playback position as a percentage (0 ~ 100).",
"peaks": "Precomputed amplitude values, normalized 0 to 1. Supplying them skips decoding entirely, which is the only path that works for cross-origin media.",
"buffered": "Buffered ranges as flat `[start, end, …]` pairs in percent, drawn as a dimmed overlay beneath the played portion.",
"bars": "Number of bars to draw. Longer `peaks` arrays are downsampled to fit; shorter ones are used as they are.",
"barWidth": "Width of a single bar, relative to the bar geometry's own coordinate space.",
"barGap": "Gap between bars, in the same units as `bar-width`.",
"barRadius": "Corner radius of a single bar.",
"height": "Height of the waveform.",
"color": "Color of the played portion. Falls back to the current text color.",
"trackColor": "Color of the unplayed portion. Falls back to the current text color at a reduced opacity.",
"bufferColor": "Color of the buffered overlay. Falls back to the current text color at a reduced opacity.",
"seekable": "Allows the position to be changed by pointer and keyboard.",
"preview": "Shows a cursor line and a time tooltip while hovering.",
"duration": "Total duration in seconds, used for the tooltip and `aria-valuetext`. Without it both fall back to a percentage.",
"normalize": "Rescales the peaks so the loudest bar reaches full height. Applied at render, so toggling it does not require decoding again.",
"sampleStrategy": "How samples are reduced to one value per bar. `peak` preserves transients and suits speech; `rms` is smoother and suits music.",
"mirror": "Reflects the waveform below the centre line at a reduced opacity.",
"step": "Distance moved by the arrow keys, as a percentage. Page Up and Page Down move ten times as far.",
"disabled": "Removes all interaction and dims the waveform."
},
"events": {
"update:modelValue": "Emitted when the position changes.",
"start": "Emitted when the user starts dragging.",
"end": "Emitted when the user stops dragging."
},
"slots": {
"tooltip": "Replaces the contents of the hover tooltip. Receives the formatted time and the hovered position."
}
}
4 changes: 4 additions & 0 deletions packages/docs/src/data/nav.json
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,10 @@
"activeIcon": "mdi-beaker",
"items": [
"introduction",
{
"title": "audio-players",
"subfolder": "components"
},
{
"title": "avatar-groups",
"subfolder": "components"
Expand Down
51 changes: 51 additions & 0 deletions packages/docs/src/examples/v-audio/misc-waveform.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<template>
<v-container class="d-flex flex-column ga-4" style="max-width: 560px;">
<v-audio-waveform
v-model="position"
:bars="96"
:duration="218"
:height="48"
:peaks="peaks"
color="primary"
mirror
></v-audio-waveform>

<div class="text-caption text-medium-emphasis text-center">
Position: {{ Math.round(position) }}%
</div>
</v-container>
</template>

<script setup>
import { shallowRef } from 'vue'

const position = shallowRef(35)
const peaks = [
0.35, 0.35, 0.57, 0.49, 0.69, 0.60, 0.85, 0.43,
0.61, 0.28, 0.43, 0.67, 0.53, 0.49, 0.36, 0.02,
0.02, 0.01, 0.01, 0.01, 0.03, 0.03, 0.01, 0.01,
0.41, 0.37, 0.65, 0.69, 1.00, 0.66, 0.68, 0.72,
0.75, 0.27, 0.25, 0.31, 0.38, 0.92, 0.48, 0.56,
0.32, 0.33, 0.02, 0.03, 0.01, 0.01, 0.02, 0.02,
0.04, 0.02, 0.29, 0.45, 0.48, 0.83, 0.87, 0.45,
0.21, 0.37, 0.33, 0.29, 0.53, 0.66, 0.83, 0.98,
]
</script>

<script>
export default {
data: () => ({
position: 35,
peaks: [
0.35, 0.35, 0.57, 0.49, 0.69, 0.60, 0.85, 0.43,
0.61, 0.28, 0.43, 0.67, 0.53, 0.49, 0.36, 0.02,
0.02, 0.01, 0.01, 0.01, 0.03, 0.03, 0.01, 0.01,
0.41, 0.37, 0.65, 0.69, 1.00, 0.66, 0.68, 0.72,
0.75, 0.27, 0.25, 0.31, 0.38, 0.92, 0.48, 0.56,
0.32, 0.33, 0.02, 0.03, 0.01, 0.01, 0.02, 0.02,
0.04, 0.02, 0.29, 0.45, 0.48, 0.83, 0.87, 0.45,
0.21, 0.37, 0.33, 0.29, 0.53, 0.66, 0.83, 0.98,
],
}),
}
</script>
60 changes: 60 additions & 0 deletions packages/docs/src/examples/v-audio/prop-density.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<template>
<div>
<v-sheet class="px-6 py-2 border-b mb-3" color="surface">
<div class="d-flex ga-2 ga-md-12 flex-wrap align-center justify-center">
<v-chip-group v-model="density" mandatory>
<v-chip text="default" value="default" filter label></v-chip>
<v-chip text="comfortable" value="comfortable" filter label></v-chip>
<v-chip text="compact" value="compact" filter label></v-chip>
</v-chip-group>

<v-chip-group v-model="features" multiple>
<v-chip text="pills" value="pills" filter label></v-chip>
<v-chip text="floating" value="floating" filter label></v-chip>
<v-chip text="detached" value="detached" filter label></v-chip>
</v-chip-group>
</div>
</v-sheet>

<v-audio
:density="density"
:detached="features.includes('detached')"
:floating="features.includes('floating')"
:peaks="peaks"
:pills="features.includes('pills')"
:skip-interval="15"
class="mx-auto"
color="primary"
max-width="520"
src="https://cdn.freesound.org/previews/612/612095_5674468-lq.mp3"
></v-audio>
</div>
</template>

<script setup>
import { shallowRef } from 'vue'

const density = shallowRef('default')
const features = shallowRef([])
const peaks = [
0.35, 0.57, 0.69, 0.85, 0.61, 0.43, 0.53, 0.36,
0.02, 0.01, 0.03, 0.01, 0.41, 0.65, 1.00, 0.68,
0.75, 0.25, 0.38, 0.48, 0.32, 0.02, 0.01, 0.02,
0.29, 0.48, 0.87, 0.21, 0.33, 0.53, 0.83, 0.98,
]
</script>

<script>
export default {
data: () => ({
density: 'default',
features: [],
peaks: [
0.35, 0.57, 0.69, 0.85, 0.61, 0.43, 0.53, 0.36,
0.02, 0.01, 0.03, 0.01, 0.41, 0.65, 1.00, 0.68,
0.75, 0.25, 0.38, 0.48, 0.32, 0.02, 0.01, 0.02,
0.29, 0.48, 0.87, 0.21, 0.33, 0.53, 0.83, 0.98,
],
}),
}
</script>
52 changes: 52 additions & 0 deletions packages/docs/src/examples/v-audio/prop-layout.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<template>
<v-container class="d-flex flex-column ga-6">
<div>
<div class="text-subtitle-2 mb-2">Default — inline</div>
<v-audio :peaks="peaks" :src="src" max-width="520"></v-audio>
</div>

<div>
<div class="text-subtitle-2 mb-2">Waveform above the actions</div>
<v-audio
:peaks="peaks"
:src="src"
max-width="520"
variant="waveform-top"
></v-audio>
</div>

<div>
<div class="text-subtitle-2 mb-2">Waveform below the actions</div>
<v-audio
:peaks="peaks"
:src="src"
max-width="520"
variant="waveform-bottom"
></v-audio>
</div>
</v-container>
</template>

<script setup>
const src = 'https://cdn.freesound.org/previews/612/612095_5674468-lq.mp3'
const peaks = [
0.35, 0.57, 0.69, 0.85, 0.61, 0.43, 0.53, 0.36,
0.02, 0.01, 0.03, 0.01, 0.41, 0.65, 1.00, 0.68,
0.75, 0.25, 0.38, 0.48, 0.32, 0.02, 0.01, 0.02,
0.29, 0.48, 0.87, 0.21, 0.33, 0.53, 0.83, 0.98,
]
</script>

<script>
export default {
data: () => ({
src: 'https://cdn.freesound.org/previews/612/612095_5674468-lq.mp3',
peaks: [
0.35, 0.57, 0.69, 0.85, 0.61, 0.43, 0.53, 0.36,
0.02, 0.01, 0.03, 0.01, 0.41, 0.65, 1.00, 0.68,
0.75, 0.25, 0.38, 0.48, 0.32, 0.02, 0.01, 0.02,
0.29, 0.48, 0.87, 0.21, 0.33, 0.53, 0.83, 0.98,
],
}),
}
</script>
52 changes: 52 additions & 0 deletions packages/docs/src/examples/v-audio/prop-peaks.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<template>
<v-container class="d-flex flex-column ga-6">
<div>
<div class="text-subtitle-2 mb-2">Peaks supplied by the server</div>
<v-audio
:peaks="peaks"
max-width="520"
src="https://cdn.freesound.org/previews/612/612095_5674468-lq.mp3"
></v-audio>
</div>

<div>
<div class="text-subtitle-2 mb-2">Peaks decoded in the browser</div>
<v-audio
crossorigin="anonymous"
max-width="520"
src="https://cdn.freesound.org/previews/612/612095_5674468-lq.mp3"
></v-audio>
</div>
</v-container>
</template>

<script setup>
// One value per bar, normalized 0 to 1 — the shape a server-side pass produces.
const peaks = [
0.35, 0.35, 0.57, 0.49, 0.69, 0.60, 0.85, 0.43,
0.61, 0.28, 0.43, 0.67, 0.53, 0.49, 0.36, 0.02,
0.02, 0.01, 0.01, 0.01, 0.03, 0.03, 0.01, 0.01,
0.41, 0.37, 0.65, 0.69, 1.00, 0.66, 0.68, 0.72,
0.75, 0.27, 0.25, 0.31, 0.38, 0.92, 0.48, 0.56,
0.32, 0.33, 0.02, 0.03, 0.01, 0.01, 0.02, 0.02,
0.04, 0.02, 0.29, 0.45, 0.48, 0.83, 0.87, 0.45,
0.21, 0.37, 0.33, 0.29, 0.53, 0.66, 0.83, 0.98,
]
</script>

<script>
export default {
data: () => ({
peaks: [
0.35, 0.35, 0.57, 0.49, 0.69, 0.60, 0.85, 0.43,
0.61, 0.28, 0.43, 0.67, 0.53, 0.49, 0.36, 0.02,
0.02, 0.01, 0.01, 0.01, 0.03, 0.03, 0.01, 0.01,
0.41, 0.37, 0.65, 0.69, 1.00, 0.66, 0.68, 0.72,
0.75, 0.27, 0.25, 0.31, 0.38, 0.92, 0.48, 0.56,
0.32, 0.33, 0.02, 0.03, 0.01, 0.01, 0.02, 0.02,
0.04, 0.02, 0.29, 0.45, 0.48, 0.83, 0.87, 0.45,
0.21, 0.37, 0.33, 0.29, 0.53, 0.66, 0.83, 0.98,
],
}),
}
</script>
Loading
Loading