2023-03-31 fredag v.13

HTML5 Music player

A small walkthrough of how a music player can be implemented using html5, css3, jquery and some php. The tutorial is barely started so bare with me for a while.

The standard HTML5 audio element

First we take a look at the standard element, which is quite fine if you're playing just one song and you're ok with the player looking different on different computers and in different browsers.

<audio id="audio1" controls> <source src="/software/music/It_Could_Be_You.mp3" type="audio/mp3"> </audio>

There are lots of tricks you can do with the player, lots of properties and lots of events, some of the things will be handled here as part of the music player but if you are interested in more details you have to look elsewhere.

Hiding the player and using our own interface

To be able to use our own design we need to first hide the built in GUI, which is very easy.

<audio id="audio1" controls hidden> <source src="/software/music/It_Could_Be_You.mp3" type="audio/mp3"> </audio>

Then we need to show something else instead, I decided for divs and a table. If you like you can probably organize it differently. For the buttons Font Awesome is used.

<div id="main-container"> <div style='clear: both;'> <audio id="audio" preload="auto" hidden> </audio> </div> </dib> <table id="controls-container"> <tr> <td id="play-pause" onclick='pause_player(); return false;'> <i id='play-button' class='fa fa-play'></i> </td> </tr> </table>

Yay, we have a button, a button which right now doesn't do anything. At this time we could cheat and set the audio element to autoplay the song but let's not do that (mostly because I will have lots of audio elements on this page and it will be slightly annoying).
If you look at the code above you'll see that we have assigned a function to the onclick event. This could very well be done through jQuery instead, and I will probably update the code to do that soon as I think it will be more clean. But let's look at the javascript code a bit.

<script type='text/javascript'> function toggle_pause() { music = $('#audio'); if (music[0].paused) { music[0].play(); $('#play-button').addClass('fa-pause').removeClass('fa-play'); } else { music[0].pause(); $('#play-button').addClass('fa-play').removeClass('fa-pause'); } } </script>

The jQuery code will try to find elements with an id named audio, and if the state of the element is paused then it will call the play method and otherwise it will call the pause method. As the Font Awesome icons are controlled via css classes we can through jQuery easily change the class to switch icon from play to pause and vice versa.
Now the button is doing something, but it is still not much of a music player.

Adding progressbar, time and volume control

Let's add some more things to our player, things that you expect to find in a music player.

<div id="main-container"> <div style='clear: both;'> <audio id="audio" preload="auto" hidden> </audio> </div> </dib> <table id="controls-container"> <tr> <td id="play-pause" onclick='pause_player(); return false;'> <i id='play-button' class='fa fa-play'></i> </td> <td id="loader-container" onselectstart="return false;"> <div id="loader"> <div id="load-progress"> <div id="play-progress"> </div> </div> </div> </td> <td id="time">00:00</td> <td id="volume-down" onclick='decrease_volume(); return false;'> <i id='fa-volume-down' class='fa fa-volume-down'></i> </td> <td id="volume-bar-container" onselectstart="return false;"> <div id="volume"> <div id="volume-level"> </div> </div> </td> <td id="volume-up" onclick='increase_volume(); return false;'> <i id='fa-volume-up' class='fa fa-volume-up'></i> </td> </tr> </table>

To make something happen when the time ticks in the audio element and when the volume is changed we need to add some event listeners that will listen to some of the events the audio element emitt. Through jQuery it can be done with something like this (so add this to the javascript section):

$(window).load(function() { audio = $('#audio'); audio[0].addEventListener('progress', function(e) { var width = 320; var percent_loaded = audio[0].buffered.end(0) / audio[0].duration; var bar_width = Math.ceil(percent_loaded * width); $('#load-progress').css('width', bar_width); }); audio[0].addEventListener('timeupdate', move_loader, false); audio[0].addEventListener('volumechange', function(e) { var width = 72; var percent_vol = audio[0].volume; var bar_width = Math.ceil(percent_vol * width); $('#volume-level').css('width', bar_width); }); }); function move_loader(e) { audio = $('#audio'); var width = 320; var percent_played = audio[0].currentTime / audio[0].duration; var bar_width = Math.ceil(percent_played * width); $('#play-progress').css('width', bar_width); time = $('#time-2'); time[0].innerHTML = convert_time_to_string(audio[0].currentTime, audio[0].duration); } function increase_volume() { audio = $('#audio-2'); var new_volume = audio[0].volume + 0.05; if (new_volume > 1) { new_volume = 1; } audio[0].volume = new_volume; } function decrease_volume() { audio = $('#audio-2'); var new_volume = audio[0].volume - 0.05; if (new_volume < 0) { new_volume = 0; } audio[0].volume = new_volume; } function convert_time_to_string(time, duration) { var time_in_sec = Math.floor(time); var duration_in_sec = Math.round(duration); var minutes = Math.floor(time_in_sec / 60); var duration_minutes = Math.floor(duration_in_sec / 60); var seconds = time_in_sec - minutes * 60; if (seconds < 10) { seconds = "0"+seconds; } if (duration_minutes > 9 && minutes < 10) { minutes = "0"+minutes; } return (minutes+":"+seconds); }

Here we have added event listeners for (load) progress, for when play progress and when volume changes. In both those cases we change the width of a div to show either the progress or the volume. We have also added the functions called from when clicking on the volume icons. We added a helper function to convert from the time that the audio element supplies to a string that we can use in our UI. It's now starting to look like the UI we got from the beginning, though we can style it like we want to.
Talking about style, the style is applied through (there are extra stuff not used yet, like showing the comming playlist and cover.

.main-container { overflow: hidden; width: 600px; } .playlist-container { height: 300px; width:300px; overflow-y: scroll; overflow-x: hidden; background: #333; float: left; } .playlist { background:#666; width:280px; padding: 0px; margin: 0px; } .image-container { float: left; width: 300px; height: 300px; text-align: center; background: #333; } .cover { margin: 0px; } .controls-container { background: #333; width: 600px; height: 30px; border-top: 1px solid #000; vertical-align: center; } .play-pause { padding: 5px; width: 20px; height: 20px; text-align: center; vertical-align: middle; } .play-button { cursor: pointer; color: #999; } #previous { padding: 5px; vertical-align: middle; } #next { padding: 5px; vertical-align: middle; } .loader-container { vertical-align: middle; } .loader { border: 1px solid #000; height: 4px; margin: 3px; width: 320px; background: #010101; } .load-progress { width: 0px; background-color: #666; height: 4px; } .play-progress { width: 0px; background-color: #999; height: 4px; } .time { color: #999; vertical-align: middle; font-weight: bold; font-family: 'Open Sans', sans-serif; } .volume-down { padding: 5px; vertical-align: middle; } .volume-bar-container { padding: 5px; vertical-align: middle; width: 80px; } .volume { border: 1px solid #000; height: 4px; margin: 3px; width: 72px; background: #010101; } .volume-level { width: 34px; background-color: #999; height: 4px; } .volume-up { padding: 5px; vertical-align: middle; } audio { background:#666; width:600px; padding: 0px; } .active a.jrplayer { color:#eee; text-decoration:none; } li a.jrplayer { color:#999; background:#333; padding:5px; display:block; } li a.jrplayer:hover { text-decoration:none; } .noselect { user-select: none; -webkit-user-select: none; -moz-user-select: none; -khtml-user-select: none; -ms-user-select: none; } i.jrplayer { cursor: pointer; color: #999; user-select: none; -webkit-user-select: none; -moz-user-select: none; -khtml-user-select: none; -ms-user-select: none; }

We could as well add ways to directly interact with the bars showing the volume and progress. We can solve that by using event listeners for the mousedown and mouseup events. Add the first code block to the jQuery "on load" block.

loader = $('#loader-2'); volume = $('#volume-2'); loader[0].addEventListener('mousedown', function(e) { track_position_slide = true; audio[0].removeEventListener('timeupdate', move_loader, false); window.addEventListener('mousemove', paint_loader, true); }); volume[0].addEventListener('mousedown', function(e) { volume_slide = true; window.addEventListener('mousemove', set_volume, true); set_volume(e); }); window.addEventListener('mouseup', function(e) { if (volume_slide == true) { volume_slide = false; window.removeEventListener('mousemove', set_volume, true); } else if (track_position_slide == true) { track_position_slide = false; window.removeEventListener('mousemove', paint_loader, true); audio[0].addEventListener('timeupdate', move_loader, false); set_track_position(e); } });

Add this part to the other part of the javascript code.

function paint_loader(e) { audio = $('#audio-2'); if (e.offsetX && e.offsetX <= 320) { $('#play-progress-2').css('width', e.offsetX); time = $('#time-2'); time[0].innerHTML = convert_time_to_string((audio[0].duration / 320) * e.offsetX); } } function move_loader(e) { audio = $('#audio-2'); var width = 320; var percent_played = audio2[0].currentTime / audio2[0].duration; var bar_width = Math.ceil(percent_played * width); $('#play-progress-2').css('width', bar_width); time = $('#time-2'); time[0].innerHTML = convert_time_to_string(audio2[0].currentTime, audio2[0].duration); } function set_track_position(e) { audio = $('#audio-2'); if (e.offsetX) { percent = e.offsetX / 320; audio2[0].currentTime = audio[0].duration * percent; } } function set_volume(e) { if (e.offsetX) { var percent = e.offsetX / 72; if (percent > 1) { percent = 1; } audio2[0].volume = percent; } }

While we're dragging the progress bar we don't want to update neither the progress nor the time string therefore we're removeing the event listener for the timeupdate in the mousedown and re-adding it in the mouseup. We have one function that is handling the dragging of the progress bar and one which actually skips to that play in the song when we release the mouse. The volume on the other hand is changed while dragging. It seemed logic that they would behave differently but with the example above you could change it to whatever you like.

To be continued...(next several songs)