Building apps with Vue.js is easy, fun, and enjoyable. You can build a working app with minimum effort. To prove that, today I’ll show you how easy it is to build your own full-featured music player. To make things even easier, we’ll use Vuetify.js, a Vue.js powered UI library, which will speed up the UI construction. I can almost feel your impatience, so let’s get started.
You can find the full source code in the GitHub repo. And here is the working demo. To follow this tutorial, you should be familiar with Vue components, Vue single file components, and ES2015 syntax.
Planning the App
Every creation starts with an idea and at least some basic planning. So first we need to decide what we want to build and what functionality we want to implement. It’s said that a picture is worth a thousand words, so let’s start with a simple sketch of the music player.
I made this wireframe so you can get a general notion of the UI we want to build. The next step is to describe the functionality we intend to implement.
As John Johnson says:
First, solve the problem. Then, write the code.
We’ll use that as a source of wisdom, and we’ll plan the app out before we start coding it.
App Components
Vue.js is a component-based framework. So we need first to split the app into individual components (five in our case, as shown in the sketch above), and to outline the features and functionality for each of them.
Title Bar
This component will contain the following parts:
- a menu on the left side
- the name of the app in the center
- three static icons on the right side
Info Panel
This component will show basic information about the currently played track:
- the track’s artist and title on the left side
- the current track’s position and duration on the right side
Control Bars
This component will contain two bars, which will include all the controls necessary to manipulate the audio tracks in the player’s playlist.
- a volume slider with an icon on the left (its appearance will change according to the volume level and when the sound is muted), and volume percentage on the right
- buttons for playing, pausing, stopping, and skipping the tracks.
- two buttons on the far right: one for repeating the current track, and one for shuffling the tracks’ order of playing
- a seek bar showing the currently played track’s position, with the ability to change it with a mouse click on the bar
Playlist Panel
This component will contain the tracks’ playlist with the following functionality:
- display a track with the proper number, artist, title, and duration properties
- select a track on single click
- play a track on double click
Search Bar
This component will offer search functionality in the cases when we want to find and play particular track(s).
Of course, the above outline cannot cover all the details and nuances, and this is perfectly fine. For now, it’s enough for us to get an overall picture of the final product. We’ll handle all the details and eventual challenges during the building process.
So let’s get into the fun part and write some code!
Getting Started
Vuetify’s quick start page offers plenty of options to get you started. We’ll use one of the pre-made Vue CLI templates called Webpack Simple. Run the following commands in the directory you want to use for this project:
First, install Vue CLI:
$ npm install -g vue-cli
Then, create the app:
$ vue init vuetifyjs/webpack-simple vue-music-player
Next, go to the app’s directory and install all dependencies:
$ cd vue-music player $ npm install
We’ll use Howler.js (a JavaScript audio library) to handle the audio parts of the music player. So we need to include it in the project too. Run the following command:
$ npm install --save howler
And finally, run the app:
$ npm run dev
The app will open on localhost:8080
in your default browser. You should see a simple Vuetify app skeleton.
Tweaking the Template
To adjust it to our needs, we need to clean up the template and tweak it a bit. Rename the App.vue file to Player.vue, open it, delete everything inside, and add the following instead:
We wrap our music player app in the v-app
component, which is required for the app to work properly. We also pass the dark
prop, to apply the Vuetify dark theme.
Now, open the main.js file, delete the original content, and add the following:
import Vue from 'vue' import Vuetify from 'vuetify' import 'vuetify/dist/vuetify.css' import Player from './Player.vue' import {Howl, Howler} from 'howler' Vue.use(Vuetify) new Vue({ el: '#app', render: h => h(Player) })
Also, open the index.html file and change the content of the
tag to Vue Music Player.
Now, in your browser, you should see an empty dark page. And voila. You are ready to start creating.
Before you start coding, it’s good to know that Vuetify offers code snippets and autocompletion for the major code editors: VS Code, Atom, and Sublime. To get the snippets, search for the extension in your favorite editor (vuetify-vscode
, or vuetify-atom
, or vuetify-sublime
).
Build the Title Bar Component
In the src directory, create a new components folder. Then, in that folder, create the PlayerTitleBar.vue file with the following content:
headset MENUAbout Vue Music Player
Version 1.0.0 OK VUE MUSIC PLAYER remove check_box_outline_blank close
Here, we use the following Vuetify components: toolbar, menu, button, icon, list, dialog, and card.
We separate the menu, the name, and the icons with the
component. To show or hide the dialog, we create the dialog: false
data property. Its value will toggle when we click on the About menu item.
Now, in the Player.vue file, import the title bar component, register it in the components object, and add it in the template.
// ADD the component in the template
Now, check the result in your browser. You should see the following:
We’ll repeat these three steps for the other four components. So when in the next sections I tell you to import, register and add a component in the template, you should follow the same procedure described here.
Build the Playlist Component
In the root directory, create a new playlist folder and add the audio files you want to play. The file names must be written with underscores between the words and a .mp3 extension at the end—for example, Remember_the_Way.mp3. Now, create an audio tracks array inside Player.vue‘s data object:
playlist: [ {title: "Streets of Sant'Ivo", artist: "Ask Again", howl: null, display: true}, {title: "Remember the Way", artist: "Ask Again", howl: null, display: true}, ... ]
Each track has title
and artist
properties, a howl
object set to null
, and a display
property set to true
.
The display
property will be used when we implement the search functionality. Now it is set to true
for all tracks, so all of them are visible.
Howler wraps an audio file in a howl
object. We set howl
to null
because we’ll populate it dynamically at the creation of the Vue instance. To do that, we use the Vue’s created
lifecycle hook.
created: function () { this.playlist.forEach( (track) => { let file = track.title.replace(/s/g, "_") track.howl = new Howl({ src: [`./playlist/${file}.mp3`] }) }) }
This will set a new Howl
object for each track in the playlist.
Now, create the PlayerPlaylistPanel.vue component and add this inside:
{{ index }} {{ track.artist }} - {{ track.title }} {{ track.howl.duration() }}
First, we pass the prop playlist
from the Player.vue file. Next, in the template, we go through each track with the v-for
directive and display the track’s index, followed by the track’s artist and title, and the duration of the track on the far right. We also use v-show
bound to the display
property. A track will be visible only if display
is true
.
Now, in the Player.vue file, we import, register, and add the playlist component in the template. Then, we bind the playlist
prop to the playlist
data property like this:
.
Let’s check the result in the browser:
There are two problems here. First, the numbers of the tracks are not correct, and second, the track’s duration is shown in milliseconds, but we want it to be in minutes. We’ll fix each of these issues by creating a formatting filter.
In the main.js file, create a numbers
filter and a minutes
filter, which will be globally accessible. Next, in PlayerPlaylistPanel.vue, we use them like this: {{ index | numbers }}
and {{ track.howl.duration() | minutes }}
.
Now, if you check the app, everything should display correctly.
Make Tracks Selectable
In the Player.vue file, add the selectedTrack: null
data property and bind it to the playlist component (:selectedTrack="selectedTrack"
). Then, we pass the prop in the PlayerPlaylistPanel.vue file (selectedTrack: Object
).
We also add a click event listener to
and then create the selectTrack()
method:
methods: { selectTrack (track) { this.$emit('selecttrack', track) } }
Now, back in Player.vue
, add the selecttrack
event to the playlist component (@selecttrack="selectTrack"
) and create the selectTrack()
method:
selectTrack (track) { this.selectedTrack = track }
Now, if you go to the playlist and click on a track, it will be selected. We can’t see it, but we can prove it in the Vue DevTools. In the following screenshot, the second track is selected:
Row and Selection Styling
The next step is to make the selection visible. To do it, we’ll bind a class which will color the selected track in orange and another class which will make even rows darker to make the tracks more distinguishable. Put the following after the v-show
directive:
:class="[{selected: track === selectedTrack}, {even: index % 2 == 0}]"
We’ll also add another class, which will show a scrollbar when the list gets too big.
We add the necessary classes at the end of the file.
And that’s it. Now, the selected track is highlighted in orange.
We’ll add the double click play functionality at the end of the next section.
Build the Player Controls Component
Let’s create the player controls now. We’ll start with the play, pause, and stop buttons.
Add the Play, Pause, and Stop Buttons
Create the PlayerControlsBars.vue component and add this inside:
stop play_arrow pause
Here, we use the Vuetify toolbar component.
There are three buttons with registered click event listeners. Let’s create the methods for them:
methods: { playTrack(index) { this.$emit('playtrack', index) }, pauseTrack() { this.$emit('pausetrack') }, stopTrack() { this.$emit('stoptrack') } }
Now, in the Player.vue file, import, register, and add the component in the template. Then, register the event listeners (@playtrack="play"
, @pausetrack="pause"
, @stoptrack="stop"
).
Next, create the index: 0
data property, which will hold the index of the current track. Then, create a computed currentTrack()
:
computed: { currentTrack () { return this.playlist[this.index] } }
And now we can start to create the play
, pause
, and stop
methods. We’ll start with the play()
method, but before that we need to create the playing: false
data property, which will indicate whether the track is playing or not. Add the following code for the play()
method:
play (index) { let selectedTrackIndex = this.playlist.findIndex(track => track === this.selectedTrack) if (typeof index === 'number') { index = index } else if (this.selectedTrack) { if (this.selectedTrack != this.currentTrack) { this.stop() } index = selectedTrackIndex } else { index = this.index } let track = this.playlist[index].howl if (track.playing()) { return } else { track.play() } this.selectedTrack = this.playlist[index] this.playing = true this.index = index }
The method takes an index as the parameter, which specifies the track to be played. First, we get the index of the selected track. Then, we make some checks to determine the value of the index
. If an index is provided as an argument and it’s a number, then we use it. If a track is selected, we use the index of the selected track. If the selected track is different from the current one, we use the stop()
method to stop the current one. Finally, if neither an index argument is passed nor a track is selected, we use the value of the index
data property.
Next, we get the howl (based on the index value) for the track and check whether it is playing. If it is, we return nothing; if it’s not, we play it.
Finally, we update the selectedTrack
, playing
and index
data properties.
Let’s now create the pause()
and stop()
methods.
pause () { this.currentTrack.howl.pause() this.playing = false }, stop () { this.currentTrack.howl.stop() this.playing = false }
Here, we just pause or stop the current track and update the playing
data property.
Let’s also make a track start playing on double click.
Add @dblclick="playTrack()"
to
in the PlayerPlaylistPanel.vue and create the playTrack()
method:
playTrack(index) { this.$emit('playtrack', index) }
Register the listener @playtrack="play"
in the Player.vue file and voila.
Add the Previous and Next Buttons
Let’s now add the previous and next buttons.
skip_previous skip_next
Create the skipTrack()
method:
skipTrack (direction) { this.$emit('skiptrack', direction) }
Register the event listener (@skiptrack="skip"
) in Player.vue.
And create the skip()
method:
skip (direction) { let index = 0 if (direction === "next") { index = this.index + 1 if (index >= this.playlist.length) { index = 0 } } else { index = this.index - 1 if (index < 0) { index = this.playlist.length - 1 } } this.skipTo(index) }, skipTo (index) { if (this.currentTrack) { this.currentTrack.howl.stop() } this.play(index) }
We first check if the direction is next
. If so, we increment the index by 1. And if the index gets bigger than the last index in the array, then we start again from zero. When the direction is prev
, we decrement the index by 1. And if the index is less than zero, then we use the last index. At the end, we use the index
as an argument for the skipTo()
method. It stops the current track and plays the next or previous.
Here is how the player looks with the buttons:
Add the Volume Slider
Add the following before all the buttons:
Here, we use the Vuetify slider component.
Add the volume: 0.5
data property, and then create the updateVolume()
method:
updateVolume (volume) { Howler.volume(volume) }
Here, we use the global Howler object to set the volume globally for all howls.
Also, we need to sync the initial Howler volume, which by default is set to 1, to the volume
property. If you don't do it, the volume will show 0.5 but will be 1 initially. To do that, we'll use the created
hook again:
created: function () { Howler.volume(this.volume) }
We want to see the volume level as a percentage on the right of the volume slider, so we add this in the template: {{this.volume * 100 + '%'}}
Add the Mute Button
Now, we add a volume icon before the slider.
volume_up volume_down volume_mute volume_off
The icon will change according to the values of the volume
and muted
properties.
Add the muted: false
data property and create the toggleMute()
method:
toggleMute () { Howler.mute(!this.muted) this.muted = !this.muted }
We use the global Howler object again to set the mute globally, and then we toggle the muted
value.
In the screenshot below, you can see how the volume slider should look:
Add the Repeat Button
Add the following after all the buttons:
repeat_one repeat_one
Add the loop: false
property in Player.vue, bind it :loop="loop"
and pass the prop (loop: Boolean
) in PlayerControlsBars.vue.
Now, let's create the toggleLoop()
method:
toggleLoop () { this.$emit('toggleloop', !this.loop) }
Now, back in Player.vue, register the event listener (@toggleloop="toggleLoop"
) and create the toggleLoop()
method:
toggleLoop (value) { this.loop = value }
At this point, we face a small problem. When a track seeks the end, it just stops. The player doesn't move to the next track, nor does it repeat the current track. To fix that, we need to add the following to the created
function after the src
property:
onend: () => { if (this.loop) { this.play(this.index) } else { this.skip('next') } }
Now, when the loop
is on, the current track will be repeated. If it's off, the player will move on the next track.
Add the Shuffle Button
Add the following after the repeat button:
shuffle shuffle
Add the shuffle: false
property in Player.vue
, bind it (:shuffle="shuffle"
), and pass the prop (shuffle: Boolean
) in PlayerControlsBars.vue
.
Now, let's create the toggleShuffle()
method;
toggleShuffle () { this.$emit('toggleshuffle', !this.shuffle) }
Now, back in Player.vue, register the event listener (@toggleshuffle="toggleShuffle"
) and create the toggleShuffle()
method:
toggleShuffle (value) { this.shuffle = value }
Now, add the following to the skip()
method after index = 0
:
lastIndex = this.playlist.length - 1 if (this.shuffle) { index = Math.round(Math.random() * lastIndex) while (index === this.index) { index = Math.round(Math.random() * lastIndex) } } else if (direction === "next") { ...
Here's how your app should look now:
Add the Seek Bar
First, in Player.vue, create the seek: 0
property. Then we'll need to watch the playing
property in order to update the seek.
watch: { playing(playing) { this.seek = this.currentTrack.howl.seek() let updateSeek if (playing) { updateSeek = setInterval(() => { this.seek = this.currentTrack.howl.seek() }, 250) } else { clearInterval(updateSeek) } }, }
This will update the seek value four times per second.
Now, create a computed progress()
:
progress () { if (this.currentTrack.howl.duration() === 0) return 0 return this.seek / this.currentTrack.howl.duration() }
Bind it (:progress="progress"
) in the template.
Now, in PlayerControlsBars.vue, pass the progress
prop (progress: Number
) and add another toolbar below the one we've already created:
Here, we use the Vuetify progress component.
Create a computed trackProgress()
, which will get the track's progress as a percentage.
computed: { trackProgress () { return this.progress * 100 }, }
And now, create the updateSeek()
method:
updateSeek (event) { let el = document.querySelector(".progress-linear__bar"), mousePos = event.offsetX, elWidth = el.clientWidth, percents = (mousePos / elWidth) * 100 this.$emit('updateseek', percents) }
Here, we get the progress bar element, which uses the .progress-linear__bar
class. I found this with the Browser DevTools. Next, we get the mouse position and the width of the bar. Then, we get the mouse click position as a percentage.
Back in Player.vue, add and register the event listener (@updateseek="setSeek"
) and create the setSeek()
method:
setSeek (percents) { let track = this.currentTrack.howl if (track.playing()) { track.seek((track.duration() / 100) * percents) } }
And boom! You can use your mouse to change the position of the played track.
Build the Info Panel Component
Create the PlayerInfoPanel.vue file with the following content:
{{ trackInfo.artist }} - {{ trackInfo.title }}
{{trackInfo.seek | minutes}}/{{trackInfo.duration | minutes}}
Here, we pass a prop trackInfo
, which we use to populate the track information in our component.
Now, back in Player.vue, import, register and add the component in the template.
Then, create a computed getTrackInfo()
:
getTrackInfo () { let artist = this.currentTrack.artist, title = this.currentTrack.title, seek = this.seek, duration = this.currentTrack.howl.duration() return { artist, title, seek, duration, } }
Next, we bind it in the template (:trackInfo="getTrackInfo"
) and voila. We get some basic info for the currently played track, as you can see in the screenshot below.
Build the Search Bar Component
Create the PlayerSearchBar.vue file with the following content:
We create a text field and add the clearable
prop to show a clearing icon when we type something.
By using v-model
, we bind it to the searchString
, which is an empty string initially. And we add an input event listener.
We also pass the playlist
prop, which we use in the searchPlaylist()
method. In this method, we use the display
property and turn it off
for each track where the title or artist doesn't match the search string, and we keep it or turn it on
for all matches. Finally, if the search string is empty or equal to null
, which happens when we clear the field with the clear button, we turn on
the display
for all tracks.
Now, back in Player.vue, import, register and add the component in the template.
Bind the playlist property (:playlist="playlist"
) and check the functionality. Here is how it should look in action:
Some Improvement Ideas
As you can see, with a clear goal and proper planning, building a Vue/Vuetify app can be really easy and enjoyable. You now have a working music player which you can use during your relaxation or coding time. Of course, there is always room for further improvements and additions, so here are some ideas you can try out to make the player even more feature-rich:
- multiple playlist support
- ability to add or remove tracks from the playlist
- drag-and-drop support
- ability to sort the tracks
- audio visualization
Conclusion
In this tutorial, we saw how easy and enjoyable it can be to build an app with Vue.js, and with Vuetify.js in particular. I hope you enjoyed building this player as much as I did. I'll be glad to see your own improved version of the player. So if you create one, just drop a demo link in the comments!
Powered by WPeMatico