Search code examples
javascriptaudiovuejs3

Audio component in Javascript with Vue3


I am trying to make a component in Vue3 to manage audio. This component will display a button that when pressed, plays an audio (just a short word) and changes the color (to show it is playing the audio).

Currently I managed to play the audio, but I have some issues that I am not sure how to resolve, which arise because I don't really understand how the Audio object works in javascript:

  1. The class .playing is never applied to the element (isPlaying always returns false!)
  2. How can I check the audio gets loaded before playing it? In case the URL is not reachable the .play() call should not be executed. But I am not sure canplaythrough event will work for me, since I just want to load the audio once (and only if the user presses the button).
  3. What is the proper way of managing events for new Audio()? Try catch or I can do something with promises?
  4. How to unmount this component correctly? this.audio.pause(); this.audio.src = null?
<template>
<button v-on:click="play" :class="{playing: isPlaying}">Audio</button>
</template>
<script>
export default {
    name: 'AudioComponent',
    data() {
        return {
            audio: null,
        }
    },
    props: {
        url: {type: String, required: true}
    },
    computed: {
        isPlaying(){
            if(this.audio !== null){
                if(!this.audio.paused && this.audio.duration > 0){
                    return true;
                }
            }
            return false;
        }
    },
    methods: {
        play(){
            if(this.audio === null){
                this.audio = new Audio(this.url);
            }
            this.audio.play();
        }
    }
}
</script>
<style scoped>
.playing {
    color: green;
}
</style>

Note that this is a simplification of the code, but should be enough to see the issue. Since I couldn't find many examples using new Audio call I am wondering if I should use the DOM calls directly instead.

Note that I am using the options API but don't mind answers with composition API too.


Solution

    1. this.audio properties are not reactive, so HTMLMediaElement events play and pause must be used.
    2. Yes, using the canplay event.
    3. You can listen for the error event as well, if that's your question.
    4. I don't think you need to do anything in particular.

    Demo

    {
        props: {
            url: {
                type: String,
                default: 'https://samplelib.com/lib/preview/mp3/sample-3s.mp3'
            }
        },
        data: () => ({
            audio: null,
            canPlay: false,
            isPlaying: false
        }),
        methods: {
            play: async function(){
                this.audio.play();
            }
        },
        mounted: function(){
            this.audio = new Audio(this.url);
            this.audio.addEventListener('canplay', () => this.canPlay = true, { once: true });
            this.audio.addEventListener('play', () => this.isPlaying = true);
            this.audio.addEventListener('pause', () => this.isPlaying = false);
            this.$watch(() => this.audio.duration, () => console.log(this.audio.paused));
        },
        template: `
            <button
                :class="{ playing: isPlaying }"
                :disabled="!canPlay"
                @click="play"
            >Audio</button>
        `
    }