A completely renderless Vue (2.x) component to help you build custom audio players.
A renderless component only provides functionality and leaves you in full control over markup and styling.
That said, it's important to understand that this component does not provide any UI whatsoever!
# npm
$ npm install vue-renderless-audio --save
# yarn
$ yarn add vue-renderless-audio
Register the component either globally or locally
// Global registration
import Vue from 'vue'
import RenderlessAudio from 'vue-renderless-audio'
Vue.component('renderless-audio', RenderlessAudio)
// Local registration
import RenderlessAudio from 'vue-renderless-audio'
export default {
components: {
RenderlessAudio
},
...
}
Basic usage in your markup looks like this
<renderless-audio src="/my-audio-file.mp3">
<template slot-scope="{ controls, time, state }">
<!-- Start building your UI here -->
</template>
</renderless-audio>
This snippet only shows the general idea behind this component and does not include any real styling yet:
<template>
<renderless-audio
:src="sources"
:autoplay="true"
:loop="true"
:volume.sync="globalVolume"
@canplaythrough="doSomething()">
<div slot-scope="{ controls, time, state }" class="my-music-player">
<!--
Progress bar as a range slider, bind progress as value and
listen for the input event to seek to a different position -->
<input
type="range"
:value="state.progress"
@input="controls.seek($event.target.value)"
min="0" max="100" step="0.1" />
<!--
Control playback with provided functions in the 'controls' object.
Conditionally display Play or Pause, depending on the current state -->
<button v-if="!state.isPlaying" @click="controls.play()">Play</button>
<button v-else @click="controls.pause()">Pause</button>
<!-- Mute/Unmute control -->
<button @click="controls.toggleMuted()">Mute/Unmute</button>
<!--
Volume control as a range slider, bind current volume as value and
listen for the input event to set it -->
<input
type="range"
:value="state.volume"
@input="controls.setVolume($event.target.value)"
min="0" max="1" step="0.1" />
</div>
</renderless-audio>
</template>
<script>
import RenderlessAudio from 'vue-renderless-audio'
export default {
components: {
RenderlessAudio
},
data() {
return {
// Control volume globally from parent (using .sync-modifier on prop)
globalVolume: 0.5,
}
}
computed: {
sources() {
return [
{ src: '/some-audio-file.mp3', type: 'audio/mp3' },
{ src: '/some-audio-file.wav', type: 'audio/wav' }
]
}
}
}
</script>
src (required)
The source of your audio file as a string or as an object array for multiple file types (containing src and type properties).
- type:
String
orArray
- default:
null
<!-- Single file type -->
<renderless-audio src="/some-audio-file.mp3"></renderless-audio>
<!-- Multiple file types -->
<renderless-audio :src="[
{ src: '/some-audio-file.mp3', type: 'audio/mp3' },
{ src: '/some-audio-file.ogg', type: 'audio/ogg' }
]"></renderless-audio>
muted
Sets player muted attribute
- type:
Boolean
- default:
false
autoplay
Sets player autoplay attribute
- type:
Boolean
- default:
false
Be aware of browsers autoplay policies. In most modern browsers, some kind of user action (eg. click) is required to autoplay media.
loop
Sets player loop attribute
- type:
Boolean
- default:
false
volume
Sets player volume, must be a floating digit between 0 and 1. (eg. 0.75
stands for 75% volume)
- type:
Number
- default:
1
playbackRate
Sets the speed at which the media is being played back. The normal playback rate is multiplied by this value to obtain the current rate, so a value of 1.0
indicates normal speed.
- type:
Number
- default:
1
start
Offsets the position where playback begins (in seconds). Must be greater than or equals 0
.
- type:
Number
- default:
0
preload
Sets the player preload attribute. Hints to the browser how or if to preload data. Must be 'none'
'metadata'
or 'auto'
- type: `String``
- default:
'auto'
crossorigin
Sets the player crossorigin attribute. Lets you configure the CORS requests for the element's fetched data. Must be 'anonymous'
or 'use-credentials'
- type: `String``
- default:
null
native
Shows the native audio player if set to true. Most likely you would only want to use this for development and debugging purposes.
- type:
Boolean
- default:
false
For some props it's useful to have "two-way-binding". You can use the .sync
modifier to automatically listen and update the parent data property. (Vue documentation: .sync modifier)
<renderless-audio
src="/my-audio-file.mp3"
:muted.sync="noSound">
</renderless-audio>
export default {
...
data() {
return {
/* will be passed as a prop but updates itself
if 'muted' state inside component changes */
noSound: true
}
}
...
}
All your markup goes into the default slot.
<renderless-audio src="/my-audio-file.mp3">
<template slot-scope="{ controls, time, state }">
<!-- Default slot: Your custom markup -->
</template>
</renderless-audio>
The slot scope exposes objects controls
, time
, state
and el
containing all necessary info and methods to control playback:
Method | Description |
---|---|
controls.play() |
Starts playback |
controls.pause() |
Pauses playback |
controls.stop() |
Pauses playback and sets the position back to the beginning |
controls.togglePlay() |
Starts or pauses playback, depending on the current state |
controls.mute() |
Mutes audio |
controls.unmute() |
Unmutes audio |
controls.toggleMuted() |
Mutes or unmutes audio, depending on the current state |
controls.setVolume(volume) |
Sets the volume (volume : floating number between 0 and 1) |
controls.seek(progress) |
Sets playback position to the given percentage value (progress : Number between 0 and 100) |
controls.toggleLooping() |
Activates or deactivates looping of the audio, depending on the current state |
controls.setPlaybackRate(rate) |
Sets playback rate |
controls.reload() |
Stops playback and reloads the audio file |
controls.download() |
Opens/downloads the audio source file |
Property / Method | Type | Description |
---|---|---|
time.played |
Number |
The time played in seconds |
time.remaining |
Number |
The time remaining in seconds |
time.duration |
Number |
The total duration of the audio file in seconds |
time.format(seconds) |
Function |
Helper function to convert seconds into a human readable format (hh:mm:ss ) |
Property | Type | Description |
---|---|---|
state.isReady |
Boolean |
Is true if player is ready for playback (first frames loaded) |
state.isWaiting |
Boolean |
Is true if player is buffering and waiting for data |
state.isMuted |
Boolean |
Is true if player is muted |
state.isLooping |
Boolean |
Is true if looping is active |
state.isLoaded |
Boolean |
Is true if the metadata is loaded |
state.hasEnded |
Boolean |
Is true if playback has reached the end and is not looping |
state.progress |
Number |
Current progress of playback in percent |
state.volume |
Number |
Current volume |
state.playbackRate |
Number |
Current playback speed |
state.buffered |
Array |
An array of all buffered timeranges, containing info about start, end and width (length) of buffered range. All numbers are percentages. |
state.currentSource |
String |
Currently loaded source file |
The el
object allows direct acces to the <audio>
element.
All native DOM events (Media events) of the underlying <audio>
element are passed to the root of the component.
Which means you can listen for native events directly on the <renderless-audio>
component (Without the .native
modifier!).
<renderless-audio
src="/my-audio-file.mp3"
@loadeddata="doSomething()"
@canplaythrough="doSomethingElse()">
...
</renderless-audio>
Additional events
In addition to the native media events, the component emits a few more useful events.
Event | Data | Description |
---|---|---|
init |
Object |
Fires when component is mounted and has been initialized. |
source-changed |
Object |
Fires when the source file changed. (Also passes the new source as source to the listener) |
stopped |
Object |
Fires when playback has stopped after calling controls.stop() |
looped |
Object |
Fires when playback has reached the end and looping is active |
All of these events pass an object with (at least) the target and controls fields to the listener:
{
// The `<audio>` element itself
target: HTMLAudioElement
// All functions described in the slot-scope 'controls' section
controls: Array<Function>
...
}
This can be very useful if you want to directly react to certain events. In the following example we're immediately starting playback after the source has changed and muting the sound after the first loop:
<renderless-audio
:src="myDynamicSource"
@source-changed="$event.controls.play()"
@looped="$event.controls.mute()">
...
</renderless-audio>
Prop update events
You can listen to prop update events with @update:propName
('propName' being the actual name of the prop) or simply use the .sync
modifier. (See: Sync props back to parent)
Usually, all method calls to the player should happen inside your markup in the default slot. Although not really recommended, there are a few ways to call the control methods from outside:
Control methods are passed on the init
event
<template>
<renderless-audio
@init="playerControls = $event.controls"
src="/some-audio-file.mp3">
</renderless-audio>
<button @click="someActionOnParent()">Button outside slot</button>
</template>
<script>
export default {
...
data() {
return {
playerControls: null
}
}
methods: {
someActionOnParent() {
this.playerControls.seek(25)
this.playerControls.play()
}
}
...
}
</script>
or
Make a reference to the component
<template>
<renderless-audio
ref="audioPlayer"
src="/some-audio-file.mp3">
</renderless-audio>
<button @click="someActionOnParent()">Button outside slot</button>
</template>
<script>
export default {
...
methods: {
someActionOnParent() {
this.$refs.audioPlayer.seek(25)
this.$refs.audioPlayer.play()
}
}
...
}
</script>
This component has not been properly tested yet, but I'm planning to target all browsers supporting Vue 2.x and the <audio>
element.