Skip to content

Build your own audio player using a renderless Vue component

License

Notifications You must be signed in to change notification settings

jschwendener/vue-renderless-audio

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

10 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

vue-renderless-audio

A completely renderless Vue (2.x) component to help you build custom audio players.

⚠️ This component is still in heavy development and should not be used in any production environment (or at your own risk)!

What does "renderless" mean?

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!

Install

# npm
$ npm install vue-renderless-audio --save

# yarn
$ yarn add vue-renderless-audio

Setup

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>

Example implementation

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>

Documentation

Props

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 or Array
  • 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

Sync props back to parent

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
        }
    }
    ...
}

Slot

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:

controls

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

time

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)

state

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

el

The el object allows direct acces to the <audio> element.

Events

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)

Access player controls from outside

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>

Browser Support

This component has not been properly tested yet, but I'm planning to target all browsers supporting Vue 2.x and the <audio> element.

License

MIT

About

Build your own audio player using a renderless Vue component

Resources

License

Stars

Watchers

Forks

Packages

No packages published