Welcome to the Treehouse Community
Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community!
Looking to learn something new?
Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.
Start your free trial

Vince Brown
16,249 PointsHow to handle event delegation with media events.
I have created a custom audio player using the HTML audio element, and a object namespace to handle all the methods for the players.
On my site most of the time the players will be there on page load, but in some instances there are audio players that get appended later.
Currently my AudioPlayer object's play and pause method delegate the event to the correct classes so the players that get appended can play and pause fine. However I have some methods that are fired when some of the media events such as "loadedmetadata" and "canplay" fire. These events work fine for players that are on the page at initial load, but do not work for players that get appended to the page later.
So my question is how do I handle event delegation with media events ?
Ex ) I have a updateDuration method that fires "onloadedmetadata" and updates elements with the audio files duration. This does not work with appended elements.
HTML
<div class="audio-player">
<div class="audio-player-controls">
<button class="audio-play"><i class="fa fa-play"></i>
<span>Play</span>
</button>
<button class="audio-pause"><i class="fa fa-pause"></i>
<span>Pause</span>
</button>
<span class="audio-currenttime audio-time">00:00</span>
<progress class="audio-progress" value="0"></progress>
<span class="audio-duration audio-time">00:00</span>
</div>
<audio class="js-audio" preload="metadata" src="song.mp3">
</audio>
</div>
jQuery
(function(root, $, window, document, undefined) {
var AudioPlayer = {
// actual html <audio> elem
$track : $( '.audio-player audio' ),
init: function () {
// bind events
this.bind();
},
bind: function () {
this.$track.on( 'loadedmetadata', this.updateDuration );
this.$track.on( 'canplay', this.getOffset );
this.$track.on( 'canplay', this.skipTime );
this.$track.on( 'timeupdate', this.updateTime );
$( 'body' ).on( 'click', '.audio-play', this.play );
$( 'body' ).on( 'click', '.audio-pause', this.pause );
},
updateDuration: function() {
console.log( 'working' );
// Store progress element
var $progress = $( this ).siblings( '.audio-player-controls')
.find( '.audio-progress' );
// store duration element
var $duration = $( this ).siblings( '.audio-player-controls')
.find( '.audio-duration' );
// set progress elem max attr to the files duration
$progress.prop( 'max', Math.floor( this.duration ) );
// set duration elems text to formatted duration
$duration.text( AudioPlayer.toHHMMSS( this.duration ) );
},
play: function() {
$el = $( this ),
$pause = $el.siblings( '.audio-pause' ),
$track = $el.parent().siblings( 'audio' );
// hide play button
$el.hide();
// show pause button
$pause.css( 'display', 'inline-block' );
// focus play button
$pause.focus();
// trigger play of audio file.
$track.trigger( 'play' );
},
pause: function() {
$el = $( this ),
$play = $el.siblings( '.audio-play' ),
$track = $el.parent().siblings( 'audio' );
// hide pause button
$el.hide();
// Show play button
$play.css( 'display', 'inline-block' );
// focus play button
$play.focus();
// pause audio file.
$track.trigger( 'pause' );
},
updateTime: function() {
// store progress elem
var $progress = $( this ).siblings( '.audio-player-controls')
.find( '.audio-progress' );
// store current time elem
var $currentTime = $( this ).siblings( '.audio-player-controls')
.find( '.audio-currenttime' );
// Continually Update progress value with the current time
$progress.prop( 'value', this.currentTime );
// Continually Update Current time with formatted play time.
$currentTime.text( AudioPlayer.toHHMMSS( this.currentTime ) );
},
skipTime: function( audio ,offset ) {
// get offset of clicked progress bar and multiply by audio file duration.
// Set the audio tracks current time
audio.currentTime = Math.floor( audio.duration ) * ( offset );
},
toHHMMSS: function( totalsecs ) {
var sec_num = parseInt(totalsecs, 10),
hours = Math.floor(sec_num / 3600),
minutes = Math.floor((sec_num - (hours * 3600)) / 60),
seconds = sec_num - (hours * 3600) - (minutes * 60);
if ( hours < 10 ) {
hours = "0" + hours;
}
if ( minutes < 10 ) {
minutes = "0" + minutes;
}
if ( seconds < 10 ) {
seconds = "0" + seconds;
}
var time = minutes + ':' + seconds;
return time;
},
getOffset: function() {
// store progress element
var $progress = $( this ).siblings( '.audio-player-controls')
.find( '.audio-progress' );
// store this ( audio file ) for access in skip time
var that = this;
// on click of progress bar get offset and run skip time method
$progress.on( 'click' , function( e ) {
var offset = e.offsetX / e.target.offsetWidth;
AudioPlayer.skipTime( that ,offset );
});
},
};
Attempt to Solve
I have tried delegating from the body to the audio element, Like I was able to with the play and pause methods but this is not working.
ex)
$( 'body' ).on( 'timeupdate', 'audio', this.updateTime );
Any help would be greatly appreciated Cheers,
Vince

Vince Brown
16,249 Pointsjack AM , They are not custom events but rather the media events that are handled by the browser when you embed audio or video Tags in your markup.
https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Media_events
After looking into it more, these media events don't bubble like a click event , which makes sense because "canplay" and "loadedmetadata" wouldn't make sense on any other element besides the media elements themselves.
So have kind of hit a road block looking into a way I could access these events for media that gets appended to the page.

jack AM
2,618 PointsI gotcha. Thanks for the info. I'm intrigued myself.

jack AM
2,618 PointsHave you seen this by chance? http://stackoverflow.com/questions/10235919/the-canplay-canplaythrough-events-for-an-html5-video-are-not-called-on-firefox
What if your callback is firing before the dynamic content has finished loaded? It mentions checking the readystate of the media element before calling back.
Also.. https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/readyState

Vince Brown
16,249 Pointsjack AM ,
I did a lot of stack overflow searching last night but did not come across that one , Thank you!
Going to play around with trying to use readystate I will let you know if I come up with a solution that works.
jack AM
2,618 Pointsjack AM
2,618 PointsJust curious, since you're creating custom events, where does 'loadedmetadata' and 'canplay' actually get triggered?
I see the $track.trigger('play') and $track.trigger('pause') in their respective areas. Which would explain the consistency of the two you mentioned as you dynamically load your content, but not the others.