Scott Hanselman

Creating a Podcast Player with HTML5 for a IE9 Pinned Site on Windows 7

August 23, 2011 Comment on this post [8] Posted in IE9 | Javascript
Sponsored By

I was looking at the HTML5Rocks podcast player sample today, and remembered I never blogged about the poor man's Podcast Player that I created for the This Developer's Life for IE9 in April. Currently there's a Flash Player on each page on TDL and one day someone will create the One HTML5 Podcast Player to Rule Them All, but so far I haven't seen it. Most are just one-offs and samples. As is my little demo here.

The HTML5Rocks player has some clever bits to it, and it's worth you doing a View Source and checking it out. There is an empty <audio/> tag at the top of the page and they ask it "can you play MP3s?" and show a compatibility message depending on what it says. You can also put text inside the <audio/> element that will be shown if the element isn't supported at all.

 $('#compatibilityMsg').html('Your browser <strong>' + 
($('#player').get(0).canPlayType('audio/mpeg') != '' ? 'can' : 'cannot') +
'</strong> play .mp3 files using HTML5 audio');

Playing audio with the <audio> tag really couldn't be simpler. The HTML5Rocks sample has some extras like setting volume and seeking that you don't see much in samples.

I wanted to create a little Podcast player for TDL that would combine things like Site Pinning and Jump Lists with some custom buttons for pinned sites and use HTML5 audio for playing the show.

So, the features:

  • Pinnable (drag the FavIcon to the TaskBar)
  • Right-click Jumplist with Recent Shows
  • Play the show with Task Buttons from the Preview
  • Previous, Next, Play, Pause.
  • Minimal code

Plus, I'm lousy at JavaScript. Here's what it looks like in action (as an animated GIF, because those are coming back in style, didn't you hear?) for those of you who don't want to try it yourselves.

Here's the general idea as code.

On the home page there is a list of all the episodes, created from a for loop going through the database of shows. I just added a non-preload audio element next to the div for each show:

<audio id="audio-2-0-4-taste" preload="none" src="http://traffic.libsyn.com/devlife/204-Taste.mp3"></audio>
<div class="episode">
<a href='post/2-0-3-education' class="showlink">
<img src="Images//203-lead.png"
alt="2.0.3 Education" width="320" height="220"/>
<div class='title'>
<p>2.0.3 Education</p>
<div class='description'>
<p>Scott and Rob talk to two developers about the role education has played in their careers</p>
</div>
</div>
</a>
</div>

Every show has an href with a class "showlink." I collect them all and give them to my jQuery plugin:

<script>

$(function(){
$(".showlink").IE9PodcastPlayer();
});
</script>

Comments appreciated as I'm still not awesome at jQuery and JavaScript. There are some options/settings that can be passed in, but I set some defaults that work for me since I did it for this one site.

Basically, I add some buttons, setup click events to go forward and backward through the list of shows passed in.

(function( $ ){
$.fn.IE9PodcastPlayer = function(options) {

var settings = {
'playerStatusDiv' : '#podcastStatus',
'currentShowIndex' : 0
};

this.each(function() {
// If options exist, lets merge them
// with our default settings
if ( options ) {
$.extend( settings, options );
}
});

function initButtons() {
try {
// Developer sample code
if(window.external.msIsSiteMode()) {
// Add buttons listener
document.addEventListener('msthumbnailclick', onButtonClicked, false);

// Add buttons
btnPrev = window.external.msSiteModeAddThumbBarButton('/images/prev.ico', 'Previous');
btnPlayPause = window.external.msSiteModeAddThumbBarButton('/images/play.ico', 'Play');
btnNext = window.external.msSiteModeAddThumbBarButton('/images/next.ico', 'Next');

// Add styles
stylePlay = window.external.msSiteModeAddButtonStyle(btnPlayPause, '/images/play.ico', "Play");
stylePause = window.external.msSiteModeAddButtonStyle(btnPlayPause, '/images/pause.ico', "Pause");

// Show thumbar
window.external.msSiteModeShowThumbBar();
}
}
catch(e) {
// fail silently
}
}

function onButtonClicked(e) {
var btnText = "",
lastIndex = 0;

if (e.buttonID !== btnPlayPause) {

switch (e.buttonID) {
case btnPrev:
btnText = "Previous";
settings.currentShowIndex--;
if (settings.currentShowIndex < 0) { settings.currentShowIndex = 0 };
break;
case btnNext:
btnText = "Next";
lastIndex = $('audio').length-1;
settings.currentShowIndex++;
if(settings.currentShowIndex >= lastIndex) { settings.currentShowIndex = lastIndex };
break;
}

stopAll();
$(settings.playerStatusDiv).text(btnText).fadeIn('slow',
function(){playPause(settings.currentShowIndex)}
);
} else {

playPause(settings.currentShowIndex);

}
}

function stopAll() {
$('audio').each(function(){
try {
$(this)[0].pause();
}
catch(e) {
// fail silently
};
});
$(settings.playerStatusDiv).hide();
}

function playPause(podcastID) {
var player = $('audio')[podcastID];
if(player.paused)
{
player.play();
$(settings.playerStatusDiv).text("Playing...").fadeIn('slow');
}
else
{
player.pause();
$(settings.playerStatusDiv).text("Paused").fadeIn('slow');
}

updatePlayPauseButton(podcastID);
}

function updatePlayPauseButton(podcastID) {
var player = $('audio')[podcastID];
try {
if(window.external.msIsSiteMode()) {
if (player.paused)
window.external.msSiteModeShowButtonStyle(btnPlayPause, stylePlay);
else
window.external.msSiteModeShowButtonStyle(btnPlayPause, stylePause);
}
}
catch(e) {
// fail silently
}
}

function addSite() {
try {
window.external.msAddSiteMode();
}
catch (e) {
alert("This feature is only available in Internet Explorer 9. Bummer.");
}
}

initButtons();
updatePlayPauseButton(0);

if(location.pathname == "/")
{
window.external.msSiteModeClearJumpList()
window.external.msSiteModeCreateJumplist('Episodes');

//This is the stuff we selected and passed in!
this.filter(":lt(10)").each(function() {
window.external.msSiteModeAddJumpListItem($(this).text().substring(41,60), this.href, '/favicon.ico');
});

window.external.msSiteModeShowJumplist();
}

};
})( jQuery );

The one part that I had to hack and still feel yucky about is at the end here with the filter() and substring(). See how I have the truncating of the show title hard coded? I was getting too much text when I passed in the whole <a/> element and children. I need to figure out a cleaner way to tell my plugin about my titles AND my audio files. Comments always appreciated.

If you REALLY want to see a jQuery plugin that will enable pinning and dynamic jump lists and isn't a piece of crap that I wrote, check out Pinify the jQuery Site Pinning Plugin or on NuGet.

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Hosting By
Hosted in an Azure App Service
August 23, 2011 4:42
Man, you're amazing!

I was working on an HTML5 audio player for my Keyvan.TV for audio podcasts that can display my podcast's logo to fill in the player space. I was working on using a background image for the container div and then putting the normal jMediaelement in there at the bottom.

Now I'm gonna try out your technique, too. Thanks for writing this down, Scott :-)
August 23, 2011 5:17
Hey Scott,

Been playing around a bit with HTML5 audio on my blog at http://devhammer.net/music, and there are a few landmines to watch out for. One that I've found is that it appears that if you use preload="none" in IE9, it won't actually load the player UI at all. I haven't tried other values for the attribute to see if they might work, but that tested pretty consistent for me across a couple machines.

The other landmine, of course, is codec support. If you want a pure HTML5 experience, you should be prepared to re-encode your MP3 content as OGG, since neither Firefox nor Opera support MP3. And unfortunately, due to the way that Firefox behaves when it runs into content it can't play, you can't even use the fallback trick of putting a download link between the audio tags, because FF will recognize the audio element, and attempt to load the file, then just throw a big old X up in your face. My workaround was just to drop a download link below the player, but some folks might not like that solution.

Will be interested to hear how you make out. Would be cool to add pinned site controls for audio...love the idea!
August 23, 2011 5:40
Quick update...just tested with the "metadata" value for the preload attribute, and that seems to work OK. So you can probably get away with using that instead of "none", though I don't know if loading the metadata will have any impact on your podcast stats.
August 23, 2011 5:41
I click play but nothing seems to happen.. :s
August 23, 2011 12:49
This is awesome - I wonder if other browsers are gong to implement jumplists for w7, and if other operating systems are going to implement this.

As for JS comments:

I *think* that looping over the elements for the settings-options extension is not needed - there's a single settings instance per the function's closure, not one per jQuery element. So either lose the loop, or put the settings object on the jQuery element (which would result with the same outcome, but take a wee bit more memory). The latter is only good if each element might later change it's own copy of the settings, which it does not as it is not part of the player's state.

nitpicker point: JS objects are not exactly like JSON objects. So you can lose the quote signs from the keys when you create the settings object at the top
August 23, 2011 20:05
I realize this post is about customizing an html 5 player but for general html5 audio and video I like html5media. Provides nice fall-back support too. I've tested with FF 5/6, IE 6,7,8,9 and whatever version of Chrome I'm running (it changes so much I'm not even sure what versions I've tested), all with no problems.
August 24, 2011 1:36
Excellent, I look forward to seeing this plugin on GitHub/BitBucket/CodePlex. ;)
August 24, 2011 1:42

Comments are closed.

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.