{"_id":"@condero/vue-audio-player","_rev":"8-15ca4b15695a89e9c4b8a744f4e1556b","name":"@condero/vue-audio-player","dist-tags":{"latest":"2.3.0"},"versions":{"1.0.0":{"name":"@condero/vue-audio-player","version":"1.0.0","keywords":["vue","vue3","audio","player","waveform","loop","ab-loop"],"license":"MIT","_id":"@condero/vue-audio-player@1.0.0","maintainers":[{"name":"cnd-jmu","email":"jonathan.muench@condero.com"},{"name":"coantis","email":"info@condero.com"}],"dist":{"shasum":"a30187e08d5bc607cb8587dca7ff38dbb65d56a5","tarball":"https://registry.npmjs.org/@condero/vue-audio-player/-/vue-audio-player-1.0.0.tgz","fileCount":2,"integrity":"sha512-BoB0pD6sCwl3nD2dX0+geTnyn9SS4k1JOBMll6XCZUudcr1TPZZKHJu3vjh1oGr7x9VkAP8/R12YGLgMJ1z/Ug==","signatures":[{"sig":"MEYCIQCCE7CVocVKemA/fM+gmb5ZqQWQeMvR2uNhopfVXrHyCQIhAPJpOUOJ+CjdV+qYGApnz7ATZVUwVQSYKGo7Sw5cWCWz","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":2044},"main":"dist/audio-player.js","type":"module","module":"dist/audio-player.js","exports":{".":{"import":"./dist/audio-player.js"},"./style.css":"./dist/audio-player.css"},"gitHead":"6f327d47836c6453f2452275fc3100113049e0fd","scripts":{"dev":"vite","build":"vite build","preview":"vite preview"},"_npmUser":{"name":"coantis","email":"info@condero.com"},"_npmVersion":"11.7.0","description":"Vue 3 audio player with waveform, repeat, speed control and A/B looping","directories":{},"_nodeVersion":"24.3.0","_hasShrinkwrap":false,"devDependencies":{"vue":"^3.5","vite":"^6.3","@vitejs/plugin-vue":"^5.2"},"peerDependencies":{"vue":"^3.3"},"_npmOperationalInternal":{"tmp":"tmp/vue-audio-player_1.0.0_1776778796856_0.9871898634516871","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@condero/vue-audio-player","version":"1.0.1","keywords":["vue","vue3","audio","player","waveform","loop","ab-loop"],"license":"MIT","_id":"@condero/vue-audio-player@1.0.1","maintainers":[{"name":"cnd-jmu","email":"jonathan.muench@condero.com"},{"name":"coantis","email":"info@condero.com"}],"homepage":"https://github.com/condero/vue-audio-player#readme","bugs":{"url":"https://github.com/condero/vue-audio-player/issues"},"dist":{"shasum":"366d4462265db23066d5d6a03c28528b865f678d","tarball":"https://registry.npmjs.org/@condero/vue-audio-player/-/vue-audio-player-1.0.1.tgz","fileCount":5,"integrity":"sha512-FjzbT5sQJIy/E1pJj6SAagYA5on5X+OPcqNjbwT4aOCPeif5VtVXgz0ZsxCeKKXiYwNANWrwt91y04uNP0GhWg==","signatures":[{"sig":"MEUCIDmtydQHSWs2vONNxqrvM9o6jmtvIb717Ns81QRhWc+GAiEAgyqDfN4f4EjU4RZrvKwsA2pAhWOj2Gfj7JfuZcHui/g=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":20981},"main":"dist/audio-player.js","type":"module","module":"dist/audio-player.js","exports":{".":{"import":"./dist/audio-player.js"},"./style.css":"./dist/audio-player.css"},"gitHead":"cf555e9a9ebc7c30a5d25aee9f6a51d0d3a19856","scripts":{"dev":"vite","build":"vite build","preview":"vite preview"},"_npmUser":{"name":"coantis","email":"info@condero.com"},"repository":{"url":"git+https://github.com/condero/vue-audio-player.git","type":"git"},"_npmVersion":"11.7.0","description":"Vue 3 audio player with waveform, repeat, speed control and A/B looping","directories":{},"_nodeVersion":"24.3.0","_hasShrinkwrap":false,"devDependencies":{"vue":"^3.5","vite":"^6.3","@vitejs/plugin-vue":"^5.2"},"peerDependencies":{"vue":"^3.3"},"_npmOperationalInternal":{"tmp":"tmp/vue-audio-player_1.0.1_1776779971996_0.3314060853924845","host":"s3://npm-registry-packages-npm-production"}},"1.0.2":{"name":"@condero/vue-audio-player","version":"1.0.2","keywords":["vue","vue3","audio","player","waveform","loop","ab-loop"],"license":"MIT","_id":"@condero/vue-audio-player@1.0.2","maintainers":[{"name":"cnd-jmu","email":"jonathan.muench@condero.com"},{"name":"coantis","email":"info@condero.com"}],"homepage":"https://github.com/condero/vue-audio-player#readme","bugs":{"url":"https://github.com/condero/vue-audio-player/issues"},"dist":{"shasum":"c596a3ac2bdc16eb63372a958e1af1edb604b944","tarball":"https://registry.npmjs.org/@condero/vue-audio-player/-/vue-audio-player-1.0.2.tgz","fileCount":5,"integrity":"sha512-Cp9LAZaV/LBNGirB9OX2mIvC+0/ot6Y9nAxUNkqi3OUjnqP3JDtoCOByfltvY4PB98Ctk+F32tISEI/5FQ+h4g==","signatures":[{"sig":"MEQCIHimf3Dh8e4nVyta0iX1Dkm8yS+JuqUGzT+qspAmikc/AiBW87l5IHopcGkMsvZWoOqbfDmfde/dZen9t/8ZwtttAg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":20981},"main":"dist/audio-player.js","type":"module","module":"dist/audio-player.js","exports":{".":{"import":"./dist/audio-player.js"},"./style.css":"./dist/audio-player.css"},"gitHead":"c9d55819392b7abe96499472a956a7de3e76c893","scripts":{"dev":"vite","build":"vite build","preview":"vite preview"},"_npmUser":{"name":"coantis","email":"info@condero.com"},"repository":{"url":"git+https://github.com/condero/vue-audio-player.git","type":"git"},"_npmVersion":"11.7.0","description":"Vue 3 audio player with waveform, repeat, speed control and A/B looping","directories":{},"_nodeVersion":"24.3.0","_hasShrinkwrap":false,"devDependencies":{"vue":"^3.5","vite":"^6.3","@vitejs/plugin-vue":"^5.2"},"peerDependencies":{"vue":"^3.3"},"_npmOperationalInternal":{"tmp":"tmp/vue-audio-player_1.0.2_1776800920258_0.5527542158006298","host":"s3://npm-registry-packages-npm-production"}},"1.0.3":{"name":"@condero/vue-audio-player","version":"1.0.3","keywords":["vue","vue3","audio","player","waveform","loop","ab-loop"],"license":"MIT","_id":"@condero/vue-audio-player@1.0.3","maintainers":[{"name":"cnd-jmu","email":"jonathan.muench@condero.com"},{"name":"coantis","email":"info@condero.com"}],"homepage":"https://github.com/condero/vue-audio-player#readme","bugs":{"url":"https://github.com/condero/vue-audio-player/issues"},"dist":{"shasum":"423312dc6411f2dd21bad498536dec86857f469b","tarball":"https://registry.npmjs.org/@condero/vue-audio-player/-/vue-audio-player-1.0.3.tgz","fileCount":5,"integrity":"sha512-a5aGZ/vAMepoJ7yqiKhJ75X4dbwe8oRcLKKYxlp1V3GzYbFURA19An9elM7MtwjKXhDn8p0ostuKEEPuOzuzxw==","signatures":[{"sig":"MEYCIQCsBR1MPV/+rdyUNvpvPlT3PWS2Orepgo43Ns4ChWNbogIhALIhv4SL33qSG65UsKxpNHme+650g9YxBo/ltVECWS+v","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":21346},"main":"dist/audio-player.js","type":"module","module":"dist/audio-player.js","exports":{".":{"import":"./dist/audio-player.js"},"./style.css":"./dist/audio-player.css"},"gitHead":"4165ea8d1c0d8696904d02cb94200b7472172851","scripts":{"dev":"vite","build":"vite build","preview":"vite preview"},"_npmUser":{"name":"coantis","email":"info@condero.com"},"repository":{"url":"git+https://github.com/condero/vue-audio-player.git","type":"git"},"_npmVersion":"11.7.0","description":"Vue 3 audio player with waveform, repeat, speed control and A/B looping","directories":{},"_nodeVersion":"24.3.0","_hasShrinkwrap":false,"devDependencies":{"vue":"^3.5","vite":"^6.3","@vitejs/plugin-vue":"^5.2"},"peerDependencies":{"vue":"^3.3"},"_npmOperationalInternal":{"tmp":"tmp/vue-audio-player_1.0.3_1776802179985_0.47239202944390324","host":"s3://npm-registry-packages-npm-production"}},"2.0.0":{"name":"@condero/vue-audio-player","version":"2.0.0","keywords":["vue","vue3","audio","player","waveform","loop","ab-loop"],"license":"MIT","_id":"@condero/vue-audio-player@2.0.0","maintainers":[{"name":"cnd-jmu","email":"jonathan.muench@condero.com"},{"name":"coantis","email":"info@condero.com"}],"homepage":"https://github.com/condero/vue-audio-player#readme","bugs":{"url":"https://github.com/condero/vue-audio-player/issues"},"dist":{"shasum":"c47965c5923294ac30e7b47ba6c6576db36d3de5","tarball":"https://registry.npmjs.org/@condero/vue-audio-player/-/vue-audio-player-2.0.0.tgz","fileCount":5,"integrity":"sha512-ILlm5AakgfKuHJ39TJuMSfqDM6G7xB0vkxLtDNzFe1zGmrNXXvJ4MCSOxpUsSY4HUj03jEpLQV5uQgfAmxEs4Q==","signatures":[{"sig":"MEUCIHH6QMpW2S+cp4o/la0lEeB5DWBXdGwIDjGTssbXLpUhAiEAyWKrxgFzzjNuuJCUZK0fSKSP4wl7YiO3uPdjRfMUp4c=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":27011},"main":"dist/audio-player.js","type":"module","module":"dist/audio-player.js","exports":{".":{"import":"./dist/audio-player.js"},"./style.css":"./dist/audio-player.css"},"gitHead":"33544ddf547fa62cbf34884aaf11aaabae369752","scripts":{"dev":"vite","test":"vitest run","build":"vite build","preview":"vite preview"},"_npmUser":{"name":"cnd-jmu","email":"jonathan.muench@condero.com"},"repository":{"url":"git+https://github.com/condero/vue-audio-player.git","type":"git"},"_npmVersion":"11.11.0","description":"Vue 3 audio player with waveform, repeat, speed control and A/B looping","directories":{},"_nodeVersion":"24.14.1","_hasShrinkwrap":false,"devDependencies":{"vue":"^3.5","vite":"^6.3","vitest":"^4.1.11","happy-dom":"^20.11.2","@vue/test-utils":"^2.4.11","@vitejs/plugin-vue":"^5.2"},"peerDependencies":{"vue":"^3.3"},"_npmOperationalInternal":{"tmp":"tmp/vue-audio-player_2.0.0_1787161488807_0.5649274080413054","host":"s3://npm-registry-packages-npm-production"}},"2.1.0":{"name":"@condero/vue-audio-player","version":"2.1.0","keywords":["vue","vue3","audio","player","waveform","loop","ab-loop"],"license":"MIT","_id":"@condero/vue-audio-player@2.1.0","maintainers":[{"name":"cnd-jmu","email":"jonathan.muench@condero.com"},{"name":"coantis","email":"info@condero.com"}],"homepage":"https://github.com/condero/vue-audio-player#readme","bugs":{"url":"https://github.com/condero/vue-audio-player/issues"},"dist":{"shasum":"cce5a2d4a962153b8e088679da7a56f267bedaa1","tarball":"https://registry.npmjs.org/@condero/vue-audio-player/-/vue-audio-player-2.1.0.tgz","fileCount":5,"integrity":"sha512-GyLjIllYg582ss9EV8wvD0ESgroH2iT5R6d8KuTI1v0xGeRkvRklF3CfecQKstukH4SzQAoNaxLJsjzMIBlW4w==","signatures":[{"sig":"MEUCICS7rxKBqOYz2jKK5UBQAn3pq34aHWQ5HLB6kuuOtfILAiEA6SDXPt8CcV8qMNlGije6TC/t0gy3NM45vmBZzw4+vIw=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":29422},"main":"dist/audio-player.js","type":"module","module":"dist/audio-player.js","exports":{".":{"import":"./dist/audio-player.js"},"./style.css":"./dist/audio-player.css"},"gitHead":"8b7868f353aa72d80dac012c01dab2100eddef98","scripts":{"dev":"vite","test":"vitest run","build":"vite build","preview":"vite preview"},"_npmUser":{"name":"cnd-jmu","email":"jonathan.muench@condero.com"},"repository":{"url":"git+https://github.com/condero/vue-audio-player.git","type":"git"},"_npmVersion":"11.11.0","description":"Vue 3 audio player with waveform, repeat, speed control and A/B looping","directories":{},"_nodeVersion":"24.14.1","_hasShrinkwrap":false,"devDependencies":{"vue":"^3.5","vite":"^6.3","vitest":"^4.1.11","happy-dom":"^20.11.2","@vue/test-utils":"^2.4.11","@vitejs/plugin-vue":"^5.2"},"peerDependencies":{"vue":"^3.3"},"_npmOperationalInternal":{"tmp":"tmp/vue-audio-player_2.1.0_1788723984951_0.10479434046525715","host":"s3://npm-registry-packages-npm-production"}},"2.2.0":{"name":"@condero/vue-audio-player","version":"2.2.0","keywords":["vue","vue3","audio","player","waveform","loop","ab-loop"],"license":"MIT","_id":"@condero/vue-audio-player@2.2.0","maintainers":[{"name":"cnd-jmu","email":"jonathan.muench@condero.com"},{"name":"coantis","email":"info@condero.com"}],"homepage":"https://github.com/condero/vue-audio-player#readme","bugs":{"url":"https://github.com/condero/vue-audio-player/issues"},"dist":{"shasum":"8e0b744fe9f8f23dc39baa4524db080315946d85","tarball":"https://registry.npmjs.org/@condero/vue-audio-player/-/vue-audio-player-2.2.0.tgz","fileCount":5,"integrity":"sha512-VbcaiizgYYlOWUrUyUP6Cj7SMFsw7/0GaXA3ivH/lP/KQnNZQPrua0lrp0rxT69oocPE9gqW9OhBP/O8wdy8gw==","signatures":[{"sig":"MEUCIHtpE9/pFMXhAZMrdDRasSyBXPiclOfa3CWEcFbbg0VPAiEA8KcyAO80DoK1o0/0C/5Uak14FOTyV4BRQ6JwGnPGF6E=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEQCICm42qUhHv/JUqAMx0uiFh4/YQyXQVnyJJ7+GRCQvmUDAiASJyXFNyjy7xl72jgqeXDkfKaYR6CpZkLuin5fLFSKGA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":40152},"main":"dist/audio-player.js","type":"module","module":"dist/audio-player.js","exports":{".":{"import":"./dist/audio-player.js"},"./style.css":"./dist/audio-player.css"},"gitHead":"6b37f2dec4c6e5ef1e6cf683a01274e5491cade7","scripts":{"dev":"vite","test":"vitest run","build":"vite build","preview":"vite preview"},"_npmUser":{"name":"cnd-jmu","email":"jonathan.muench@condero.com"},"repository":{"url":"git+https://github.com/condero/vue-audio-player.git","type":"git"},"_npmVersion":"11.11.0","description":"Vue 3 audio player with waveform, repeat, speed control and A/B looping","directories":{},"_nodeVersion":"24.14.1","_hasShrinkwrap":false,"devDependencies":{"vue":"^3.5","vite":"^6.3","vitest":"^4.1.11","happy-dom":"^20.11.2","@vue/test-utils":"^2.4.11","@vitejs/plugin-vue":"^5.2"},"peerDependencies":{"vue":"^3.3"},"_npmOperationalInternal":{"tmp":"tmp/vue-audio-player_2.2.0_1790357291711_0.35659783466508466","host":"s3://npm-registry-packages-npm-production"}},"2.3.0":{"_id":"@condero/vue-audio-player@2.3.0","bugs":{"url":"https://github.com/condero/vue-audio-player/issues"},"dist":{"shasum":"2669ee933811841ab3f910d4b7f1461dcb7dabf4","tarball":"https://registry.npmjs.org/@condero/vue-audio-player/-/vue-audio-player-2.3.0.tgz","fileCount":5,"integrity":"sha512-ajoEgWN7q+mnkuUzYrcw016MUs88Hy85ELkWt6NwCqtYqu/eP00Ktdc4o42yv1g3hZwLIOWz9/wvAfk8eAMeiQ==","signatures":[{"sig":"MEUCIADfYIkEe5RJKoDyOvPKRHbDpI+yI5rAiyxlR/vDgLUuAiEAnMCAYRxAHNS3/dKgIAPwKS6ka68J2j+75fFa3C//f/k=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCLTmJLVUKR9YiD40sK0rfLvRqO/3ZwNmJhYcJKAaFFvgIgT+iQImTKHRl2mgar05EiR6CKZMpF3VltpE9uhmBLDwc="}],"unpackedSize":43258},"main":"dist/audio-player.js","name":"@condero/vue-audio-player","type":"module","module":"dist/audio-player.js","exports":{".":{"import":"./dist/audio-player.js"},"./style.css":"./dist/audio-player.css"},"gitHead":"5a094b7feff28f1da0b0e948c3261213e9172d92","license":"MIT","scripts":{"dev":"vite","test":"vitest run","build":"vite build","peaks":"node scripts/generate-peaks.mjs","preview":"vite preview"},"version":"2.3.0","_npmUser":{"name":"cnd-jmu","email":"jonathan.muench@condero.com"},"homepage":"https://github.com/condero/vue-audio-player#readme","keywords":["vue","vue3","audio","player","waveform","loop","ab-loop"],"repository":{"url":"git+https://github.com/condero/vue-audio-player.git","type":"git"},"_npmVersion":"11.11.0","description":"Vue 3 audio player with waveform, repeat, speed control and A/B looping","directories":{},"maintainers":[{"name":"cnd-jmu","email":"jonathan.muench@condero.com"},{"name":"coantis","email":"info@condero.com"}],"_nodeVersion":"24.14.1","_hasShrinkwrap":false,"devDependencies":{"vue":"^3.5","vite":"^6.3","vitest":"^4.1.11","happy-dom":"^20.11.2","@vue/test-utils":"^2.4.11","@vitejs/plugin-vue":"^5.2"},"peerDependencies":{"vue":"^3.3"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/vue-audio-player_2.3.0_1790600267669_0.7465931589473702"}}},"time":{"created":"2026-04-21T13:39:56.762Z","modified":"2026-09-28T12:57:48.021Z","1.0.0":"2026-04-21T13:39:57.067Z","1.0.1":"2026-04-21T13:59:32.124Z","1.0.2":"2026-04-21T19:48:40.383Z","1.0.3":"2026-04-21T20:09:40.129Z","2.0.0":"2026-08-19T17:44:48.963Z","2.1.0":"2026-09-06T19:46:25.087Z","2.2.0":"2026-09-25T17:28:11.797Z","2.3.0":"2026-09-28T12:57:47.765Z"},"bugs":{"url":"https://github.com/condero/vue-audio-player/issues"},"license":"MIT","homepage":"https://github.com/condero/vue-audio-player#readme","keywords":["vue","vue3","audio","player","waveform","loop","ab-loop"],"repository":{"url":"git+https://github.com/condero/vue-audio-player.git","type":"git"},"description":"Vue 3 audio player with waveform, repeat, speed control and A/B looping","maintainers":[{"name":"cnd-jmu","email":"jonathan.muench@condero.com"},{"name":"coantis","email":"info@condero.com"}],"readme":"# @condero/vue-audio-player\n\nVue 3 audio player with optional waveform visualization, repeat, speed control and A/B looping.\n\n## Features\n\n- Progressive playback through the native audio element\n- Optional client-side waveform visualization via Web Audio API (no external dependencies)\n- Repeat — loop the entire track\n- Speed — configurable rate presets via the `speeds` prop (default `0.5x`–`1.2x` with fine steps around `1.0x`), pitch-preserving by default (`preservesPitch`)\n- A/B Loop — mark two positions within a track and loop that section\n- Dark/Light Mode — supports Bootstrap 5.3 theme variables and `prefers-color-scheme`\n- Responsive — desktop and mobile (touch-friendly)\n- Queue-friendly — events and exposed `play()`/`pause()` for playlist-driven hosts, with `playWhenReady` play intent, position/`rate` reporting, relative seeking (`seekBy`), a `compact` variant and per-control switches for embedding in small bars\n\n## Installation\n\n```bash\nnpm install @condero/vue-audio-player\n```\n\n## Usage\n\n```vue\n<script setup>\nimport { AudioPlayer } from '@condero/vue-audio-player'\nimport '@condero/vue-audio-player/style.css'\n</script>\n\n<template>\n  <AudioPlayer src=\"/path/to/file.mp3\" />\n</template>\n```\n\n## Props\n\n| Prop | Type | Required | Description |\n|------|------|----------|-------------|\n| `src` | String | yes | URL to the audio file |\n| `autoplay` | Boolean | no | Starts playback after the initial audio load. Browser autoplay policy may require a manual tap. |\n| `playWhenReady` | Boolean | no | Defaults to `false`. Play intent that survives src swaps: while `true`, every load starts playing on `ready` — the initial one and every swap (unlike `autoplay`, which covers the initial load only). Reactive: flipping it to `false` before the next `ready` cancels the auto-play. **Gesture safety:** set it to `true` from a user gesture (e.g. the click that starts the queue) or the browser may reject the play; a rejection is logged as a warning and the player stays paused — if you need the rejection itself (`NotAllowedError`), sequence `play()` from `@ready` instead. |\n| `waveform` | Boolean | no | Defaults to `false`. When enabled, fetches and decodes the complete file in the background to generate a waveform. |\n| `peaks` | Array | no | Precomputed waveform values from `0` to `1`. Providing peaks avoids the waveform fetch. |\n| `variant` | String | no | `'full'` (default) or `'compact'`. Compact shrinks the waveform bar, buttons and padding for embedding in small player bars (~76px tall with `times` hidden). Colors and Bootstrap 5.3 theming are unchanged. |\n| `controls` | Object | no | Per-control visibility switches, all `true` by default: `{ play, volume, abLoop, repeat, times }` (`times` is the current-time/duration row). A partial object only hides what it names `false`, e.g. `:controls=\"{ abLoop: false }\"`. |\n| `speeds` | Array | no | Playback-rate presets for the speed select, treated as a set: rendered deduped, filtered to finite values > 0 and sorted ascending — the prop is a set, the component owns the order. Defaults to `0.5`–`1.2` in 10% steps plus the fine steps 1.05/1.1. If the active rate is no longer offered after a prop change, it snaps to the nearest preset. For choir practice a fine-grained ladder up to 2.0 works well, e.g. `:speeds=\"[0.5, 0.75, 0.95, 1, 1.05, 1.2, 1.3, 1.5, 1.75, 2]\"`. |\n| `preservesPitch` | Boolean | no | Defaults to `true`: the element stretches time when the rate differs from 1, so playback stays in tune (choir training at 0.6x keeps the pitch). `false` gives the cassette-style speed pitch shift. Applied to the element's standard `preservesPitch` and the legacy `webkitPreservesPitch`, and re-asserted after every load. |\n| `pauseFade` | Boolean | no | Defaults to `true`. De-clicks pause: the element volume ramps down over ~15 ms, the player pauses in silence, and the volume is restored — the stream never cuts at full amplitude, which is audible as a snap on some output chains. Set `false` for a hard, immediate pause. A `play()` during the fade cancels it; `load()` and volume changes reset it. On iOS Safari the element's `volume` is read-only, so the fade degrades to a ~15 ms delayed pause there. Note that `paused` fires after the fade completes. |\n\n## Events\n\n| Event | Payload | Description |\n|-------|---------|-------------|\n| `ready` | `{ src }` | The current source can play: the first `canplay` after each (re)load. Sequence `play()` here when swapping `src` programmatically (mobile Safari requires it). A stall recovery does not re-fire it. The `src` echo lets queue hosts drop stale events after rapid swaps. |\n| `ended` | `{ src }` | The current source finished naturally. Not emitted in repeat mode — the track loops silently. |\n| `error` | `MediaError`, `{ src }` | The audio element failed. First payload is the native `MediaError` (code 4 = unsupported/forbidden source, 2 = network failure) or the raw error event when the element reports none — useful for detecting expired signed URLs. The second payload echoes the `src` that failed. |\n| `playing` | — | Playback started — mirror of the element's `play` event, whether from the play button or a programmatic `play()`. |\n| `paused` | — | Playback paused — mirror of the element's `pause` event. |\n| `time` | `{ currentTime, duration, progress, rate }` | Emitted on the element's `timeupdate` (and when metadata arrive or a seek settles). `duration` is `null` while unknown (metadata pending or a live/rangeless stream); `progress` is the `0..1` ratio, `0` while `duration` is unknown; `rate` is the active playback rate. Feed it to a rate-aware `navigator.mediaSession.setPositionState()` so the lock-screen scrub position does not drift when the rate ≠ 1. |\n\nPayload note: events that were payload-less may have gained payloads in 2.2.0 (`ready`/`ended` now carry `{ src }`, `error` gained a second argument); existing handlers that ignore arguments keep working.\n\n## Exposed methods\n\n```vue\n<script setup>\nimport { ref } from 'vue'\n\nconst player = ref(null)\n</script>\n\n<template>\n  <AudioPlayer\n    ref=\"player\"\n    :src=\"currentSrc\"\n    @ended=\"advanceToNextTrack\"\n    @error=\"refetchSignedUrl\"\n    @ready=\"player.play()\"\n  />\n</template>\n```\n\n- `play()` — starts playback and returns the audio element's `play()` promise unchanged, so an autoplay-policy rejection (`NotAllowedError`) reaches the caller.\n- `pause()` — pauses playback; honors the `pauseFade` prop (a faded pause completes after ~15 ms).\n- `seekTo(seconds)` — seeks to an absolute position. Clamped to `[0, duration]`; queued and applied after metadata arrive if they are not loaded yet (like timeline seeks, it never reloads the element).\n- `seekBy(delta)` — relative seek for skip controls (e.g. MediaSession `seekforward`/`seekbackward` ±15s), clamped the same way. A no-op while duration is unknown, so hosts never track `currentTime` themselves.\n- `position` — current playback position in seconds (read-only).\n- `duration` — duration in seconds, or `null` while unknown (metadata pending or a live/rangeless stream) — same semantics as the `time` payload.\n- `playbackRate` — the active playback rate (read-only; change it via the `speeds` select).\n\nThe raw audio element is intentionally not exposed; position, volume and rate stay owned by the component.\n\n## Queue hosts\n\nA playlist-driven host swaps `src` per track and keeps one long-lived player instance. With `playWhenReady`, the ready→play sequencing moves into the component:\n\n```vue\n<script setup>\nimport { ref } from 'vue'\nimport { AudioPlayer } from '@condero/vue-audio-player'\nimport '@condero/vue-audio-player/style.css'\n\nconst player = ref(null)\nconst currentSrc = ref(null)\nconst playWhenReady = ref(false)\nconst queue = [/* track urls */]\n\nfunction startQueue() {\n  // The first `true` must originate from a user gesture (autoplay policy).\n  playWhenReady.value = true\n  currentSrc.value = queue[0]\n}\n\nfunction advance({ src }) {\n  if (src !== currentSrc.value) return // stale event from a rapid swap\n  currentSrc.value = queue[queue.indexOf(src) + 1]\n}\n</script>\n\n<template>\n  <AudioPlayer\n    ref=\"player\"\n    :src=\"currentSrc\"\n    :play-when-ready=\"playWhenReady\"\n    variant=\"compact\"\n    :controls=\"{ times: false, abLoop: false, repeat: false }\"\n    @ready=\"onReady\"\n    @ended=\"advance\"\n    @error=\"refetchSignedUrl\"\n    @time=\"onTime\"\n  />\n</template>\n```\n\n- `playWhenReady` plays every load on `ready`, swaps included; flipping it to `false` before the next `ready` cancels the auto-play. A policy rejection is logged and the player stays paused — sequence `play()` from `@ready` instead if you need to handle `NotAllowedError` yourself (both patterns can be combined; the extra `play()` on a playing element is a harmless no-op).\n- The `src` echo in `ready`/`ended`/`error` payloads lets you drop stale events after rapid swaps (see `advance` above).\n- The `time` event feeds Media Session so the lock-screen scrub position tracks the queue:\n\n```js\nfunction onTime({ currentTime, duration, rate }) {\n  if ('mediaSession' in navigator && duration !== null) {\n    navigator.mediaSession.setPositionState({\n      duration,\n      position: Math.min(currentTime, duration),\n      playbackRate: rate,\n    })\n  }\n}\n```\n\n- MediaSession skip actions map to the exposed `seekBy()`: `navigator.mediaSession.setActionHandler('seekforward', () => player.value.seekBy(15))` and `setActionHandler('seekbackward', () => player.value.seekBy(-15))`.\n\n`variant=\"compact\"` plus hiding `times`, `abLoop` and `repeat` yields a bar that fits a ~76px container (waveform 28px, 32px play button, 6px padding); `controls` hides anything else the host drives itself (e.g. `play: false` when the host renders its own button and drives the exposed `play()`/`pause()`).\n\n## Swapping sources\n\nChanging the `src` prop loads the new source paused at `0:00` on the same audio element: position, buffered range, duration and A/B loop points reset, while volume, playback rate and repeat survive. `autoplay` applies to the initial load only — after a swap, either wait for `ready` and call the exposed `play()`, or set `playWhenReady` and let the component do the sequencing for every load.\n\n## Precomputing waveform peaks\n\nPeaks are plain numbers from `0` to `1` — generate them once with ffmpeg and pass them via the `peaks` prop; the browser then renders the waveform without fetching or decoding anything.\n\nThis repo ships the demo track's peaks (`public/peaks.json`), generated by the same pipeline the production media backend uses:\n\n```bash\nnpm run peaks                                 # demo track -> public/peaks.json\nnode scripts/generate-peaks.mjs INPUT [OUT]   # any file ffmpeg can decode\n```\n\nThe pipeline treats ffmpeg purely as a decoder — first audio stream to raw mono 8 kHz 16-bit PCM on stdout:\n\n```bash\nffmpeg -hide_banner -loglevel error -i INPUT \\\n  -map a:0 -ac 1 -ar 8000 \\\n  -f s16le -acodec pcm_s16le pipe:1\n```\n\nThe script buckets that stream: every sample becomes `|s| / 32768` (rounded to 4 decimals), maxima accumulate into 1024-sample coarse buckets, and those are max-spread onto the 800 final buckets — so any tool consuming the PCM pipe reproduces the same numbers. The artifact next to the audio bytes:\n\n```json\n{\n  \"version\": 1,\n  \"bucket_count\": 800,\n  \"sample_rate\": 8000,\n  \"duration_ms\": 95243,\n  \"peaks\": [0.0, 0.0123, \"...\"]\n}\n```\n\nPass its `peaks` array to the prop: `<AudioPlayer :peaks=\"artifact.peaks\" />`. Values saturate at `1.0` where the master exceeds full scale — PCM is 16-bit, so the pipeline cannot represent overshoot.\n\n## Player states\n\nThe composable distinguishes initial loading from later playback interruptions:\n\n- `isLoading` is true only from `load()` until the first `canplay` event.\n- `isBuffering` is true when an already-loaded track is waiting for more data.\n- `isSeeking` is true while the media element is moving to a new position.\n- `isPlaying` follows the media element's play and pause events.\n\nControls are disabled only during `isLoading`; buffering and seeking show a small non-blocking indicator.\n\n## Streaming and seeking\n\nBy default, the player does not fetch or decode the audio file itself. The native audio element handles playback progressively, so playback can begin as soon as the browser has enough data. This works with any plain HTTP audio endpoint; endpoints that support HTTP Range requests (`206 Partial Content`) provide better seeking and buffering behavior.\n\nThe timeline seeks the existing audio element without replacing its `src` or calling `load()`. If duration metadata is not available yet, ratio-based timeline seeks are ignored; explicit time seeks are queued and applied after metadata arrives. Live or rangeless streams with an infinite duration do not support ratio-based seeking.\n\n## Breaking changes in 2.0.0\n\nWaveform decoding is no longer enabled by default. Set `waveform` to opt into the client-side full-file decode, or pass backend-precomputed `peaks`. The new `autoplay` prop replaces DOM-based play-button workarounds.\n\n## Development\n\n```bash\nnpm install\nnpm run dev      # Demo with theme toggle at localhost:5173\nnpm run build    # Build library (dist/)\nnpm test         # Run the unit tests\n```\n\n## Local usage without publishing\n\n```bash\nnpm install ../path/to/vue-audio-player\n```\n\nAfter making changes: run `npm run build` in the player project, then reinstall in the target project.\n","readmeFilename":"README.md"}