HTML5 & audio tag makes your development easier, or not!

| No Comments

Introduction

Until HTML5, in order to play a sound it was needed to use a flash codec with "embed" or "object" tag and to define a flash player. Because flash is less and less supported by Smartphones this kind of implementation is starting to be out-of-date.

Today with HTML5 it seems to be easier to play a sound, seemingly you just need to use "audio" tag and it works. In this article we'll see that is not as easy as it seems. In a first part we'll see how to implement audio HTML5 tag, in the second part we'll define the problem that use of HTML5 can create and give you a way to solve it.

Audio tag

As I told you HTML5 make your development easier!  If you go on W3C website you can find this example:

<audio controls="controls">
  <source src="horse.ogg" type="audio/ogg">
  <source src="horse.mp3" type="audio/mpeg">
Your browser does not support the audio element.
</audio>

As you can see, it looks very easy to use!  But if you look precisely, first you need to have two sound formats due to browsers which don't support the same format. Until now it still looks very easy to use.

Problems encountered & fix

What are these problems I'm telling you since the beginning? If you look at the audio documentation in W3C you can see HTML5 audio is supported only with the newest browsers. So what happened if you use only HTML5 and your visitor uses Internet Explorer 8 which doesn't support HTML5? When they try to play the sound they receive an alert "Your browser does not support the audio element.". Maybe you're thinking: "Anyway, Internet Explorer is an old version, we don't care!", If you look the market share browsers (still in W3C) you can see in September 2012 Internet Explorer 8 and lower is used by 58.5% of the Internet Explorer user, consequently 9.6% of the total market. So, if you have professional website you can put away 10% of your possible visitors.

What are we able to do? In IDM, I developed this solution. If your browser doesn't support HTML5, you use the old flash system. And if they both are not supported, you only redirect the user to the sound (for example old Android don't support HTML5 and don't have the flash player).

 

How do you detect if HTML5 is supported by your browser? I find this function:

function supportAudioHtml5(){

    var audioTag  = document.createElement('audio');

    try{

    return (

            !!(audioTag.canPlayType)  /* function canPlayType is defined */

            && ( ( "no" != audioTag.canPlayType("audio/mpeg")  && "" != audioTag.canPlayType("audio/mpeg") ) /* test mp3 audio format */

            || ( "no" != audioTag.canPlayType("audio/ogg")  && "" != audioTag.canPlayType("audio/ogg") ) /* test ogg audio format */

           ));    

    }catch(e){

        return false;

    }

}

If this function return true, you can use HTML5, if not you need to know if flash is installed with this function:

function flashAvailable(){

    var flashinstalled = 0;

    var flashversion = 0;

    MSDetect = "false";

    if (navigator.plugins && navigator.plugins.length){

        x = navigator.plugins["Shockwave Flash"];

        if (x){

            flashinstalled = 2;

            if (x.description){

                y = x.description;

                flashversion = y.charAt(y.indexOf('.')-1);

            }

        }

        else

            flashinstalled = 1;

        if (navigator.plugins["Shockwave Flash 2.0"]){

            flashinstalled = 2;

            flashversion = 2;

        }

    }

    else if (navigator.mimeTypes && navigator.mimeTypes.length){

        x = navigator.mimeTypes['application/x-shockwave-flash'];

        if (x && x.enabledPlugin)

            flashinstalled = 2;

        else

            flashinstalled = 1;

    }

    else{// it's an MSVersion

        for(var i=7; i>0; i--){

            flashVersion = 0;

           try{

              var flash = new ActiveXObject("ShockwaveFlash.ShockwaveFlash." + i);

              flashVersion = i;

              return (flashVersion > 0)

            }

            catch(e){

            }

        }

    }    return (flashinstalled == 2);

}

If this function return true you can use flash. Or else you need to create a pop-up like that:

window.open(source_mp3,'Sound',"menubar=no, status=no, scrollbars=no, menubar=no, width=200, height=100");

We use the mp3 format in this case because mp3 is supported by all the OS players. But we can test if the OS player support the format and give him the best format.

Now can I play sound in all the browsers? No, not really! If you use jQuery to play you sound like use you need to fix some other bugs! If HTML5 is supported you can play the sound with: 

                yourAudioObject.play();

But there is a problem here! Because you need to use two types of sound formats your browser needs to get the best formats for him. The problem is, to play the sound the play() method use the "src" attribute of your object, and your browser set the "currentSrc" attribute with the good sound file for you. So you need to do : youAudioObject.src = yourAudioObject.currentSrc;

But here there is another problem in Android 4.0, the default browser doesn't set the currentSrc and any other attribute! As a consequence, you need to set the "src" attribute for him. But how do you detect the best format for the browser ?

There is a method on you sound objet which is call canPlayType("format type"). If you are on Firefox then:

                youAudioObject.canPlay("audio/mpeg") is false

youAudioObject.canPlay("audio/ogg") is true

If all of your format are not supported, try the flash, and if it doesn't available to use a redirect.

Now imagine, you gave a source a file which is not available (wrong path for example).The sound will never play, and you need to inform your visitor. There is an HTML attribute "onerror" which can help us.

onerror="playSoundException()"

If there is an audio error call playSoundException() and for example display an alert "Sound are not available". But still once, there is a problem. In internet explorer 9 if you load the page and there is an audio error the alert will be show directly even if the visitor doesn't want to play the sound. To fix this you can for example add an attribute data-clicked and test on the playSoundException() if this attribute is set.

function playSoundException(div){

    if($("."+div).attr("data-clicked") == "1"){ // if there  is a click

        alert("Apologize, the sound is not available."); // show alert

        $("."+div).attr("data-clicked","0");// set the click to 0

    }

}

But now, you need to had on click this line:  $(this).attr("data-clicked","1");

And also when sound are finished to be played: $(this).attr("data-clicked","0");

 

To conclude, as you can see HTML5 can be really easy to use if you don't want to support old browsers. But if you need to support most of the old browsers version there is a lot of particularly case which can create problems.

 

Our code example :

<a class="sound play_sound_button speaker"

        title="Your title"

        data-audio_div_id='audio_wotd'>

    </a>

<audio id="audio_wotd" onerror="playSoundException('play_sound_button')">

<source class="audio_file_source" src="yourmp3.mp3" type="audio/mpeg" />

<source class="audio_file_source" src=" yourOgg.ogg" type="audio/ogg" />

</audio>

Here, we don't click directly on the audio, but on link which in fact a speaker picture (speaker class).

jQuery(document).ready(function(){

    $(".play_sound_button").click(function() {

        $(this).attr("data-clicked","1");

        $(this).removeClass("speaker");

        $(this).addClass("spinner");

        var audio_div_id = $(this).attr("data-audio_div_id");

        playSound(audio_div_id,this);

    });

});

Onclick on the « play_sound_button » we replace the speaker image by a spinner. We get the audio id to call which is on the link attribute "data-audio_div_id".And we call our function playSound.

function playSound(audio_id,div){

var defaultType = ".mp3"; // the default type to format to use if html5 doesn't work    var classSources = ".audio_file_source"; // the class for all source (unique by audio)

    var audio = $('#' + audio_id);

    if (supportAudioHtml5() && audio.length == 1){

        var sound=audio.get(0);

        sound.addEventListener('canplaythrough', function() { // if we can play

            $(div).removeClass("spinner"); // loading complete we remove the spinner

            $(div).addClass("speaker"); // and put the speaker

        }, false);

        sound.addEventListener("ended", function() {

            $(div).attr("data-clicked","0");

        });

        var sourceAvailable = true;

        if(sound.currentSrc != ""){ // if there is a preferred sound browser

            sound.src=sound.currentSrc; // set preferred sound by browser

        }

        else if(sound.src == ""){

            sourceAvailable = false;

            $(classSources).each(function(){// for each sources

                if(sound.canPlayType($(this).attr("type"))){

                    sound.src = $(this).attr("src");

                    sound.currentSrc = $(this).attr("src");

                    sourceAvailable = true;

                }

            });

        }

        if(!sourceAvailable){  // if there is any sound available            playSoundWithoutHTML5($(classSources+'[src$="'+defaultType+'"]').attr("src"),div);

        }

        sound.play();

    }else{ playSoundWithoutHTML5($(classSources+'[src$="'+defaultType+'"]').attr("src"),div);

    }

}

function playSoundWithoutHTML5(source_mp3,div){

    if(flashAvailable()){// if flash is available (function describe in this article)

        var mp3_file ="speaker.swf?song_url=" +  source_mp3 + "&autoplay=true')";

        var sPlayer;

        if (navigator.plugins && navigator.mimeTypes && navigator.mimeTypes.length) { // netscape plugin architecture

            sPlayer = "<embed type='application/x-shockwave-flash' src='" + mp3_file + "' width='0' height='0'></embed>"

        } else { // PC I

            sPlayer = "<object type='application/x-shockwave-flash' width='0' height='0' codebase='http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0' data='" + mp3_file + "'><param name='wmode' value='transparent'/><param name='movie' value='" + mp3_file + "'/><embed src='" + mp3_file + "' width='0' height='0' ></embed></object>"

        }

    }

    else{// if flash is not available redirect

        window.open(source_mp3,'Sound',"menubar=no, status=no, scrollbars=no, menubar=no, width=200, height=100");

    }

    $("body").append(sPlayer);

    $(div).removeClass("spinner");// loading complete we remove the spinner

    $(div).addClass("speaker");// set preferred sound by browser

    $(div).attr("data-clicked","0");

}

Sébastien Ferry

Design & development engineer at IDM

 

New GA features: page load time breakdown and visitor flow on site

| No Comments
Hi all. Here is a quick Christmas post, everybody is eager to read to have something to say after the turkey and before the champagne, or at any moment you want...

It is all about recent features upgrades in Google Analytics, so mainly a post for our PitchLeads team but not only. You'll see.

First, Analytics is now providing a breakdown of the page speed service. In details, GA now provides:
  • Avg. Redirection Time
  • Avg. Domain Lookup Time
  • Avg. Server Connection Time
  • Avg. Server Response Time
  • Avg. Page Download Time

To access this and get more details, read the Google post here:
http://analytics.blogspot.com/2011/12/greater-insights-from-site-speed-report.html


Second, Analytics is providing a synthetic chart to read how visitors flow in and on the website. It is not only a great illustrative way to display entrance routes and exits pages but the history of the diagram is worth reading :-)
Please get there:
http://analytics.blogspot.com/2011/12/sankey-diagrams-and-flow-over-hundred.html

I would be very happy to discuss these two features with some of you beginning of 2012. Arnaud, Mikaël and Tristan in particular ;-)

I wish you a Merry Christmas!

Storing passwords in database

| No Comments
Security is tricky subject. In fact, we should never think we know about security. The way we store passwords in database is an example of how we might do things the wrong way. We may think storing the MD5 hash of a password, but this is very unsafe as an attacker could use a rainbow table to retrieve the password.

Here are two interesting entries on Stack Overflow:

What they recommend is storing the salted hash of the password. The salt should be different for each password, and it should be a random ASCII string stored along with the password.

Here is an interesting video about virtualization, Java and memory management:
Video: Virtualization and Java: An Introduction to Memory Management

Test-driven development

| No Comments
I want to remind a crucial point of test-driven development: when writing unit tests for an application, you need to make sure new tests fail before implementing the functionality that will make them pass.

The steps, as listed from Wikipedia, are:
  • add a test
  • run all tests and see if the new one fails
  • write some code
  • run the automated tests and see them succeed
  • refactor code
  • repeat.

We have an ASP.NET MVC (v3) application which, when deployed on our Microsoft IIS 7.5 server in RELEASE mode, produced the following error:

Server Error in '/' Application.
The incoming request does not match any route.

However, the application was working fine in DEBUG mode.

The issue was coming from the attribute-based routing provided by IT Cloud Web Routing. To discover routes, we call IT Cloud Web Routing's DiscoverMvcControllerRoutes() method in the Global.asax.cs file of our application:

public static void RegisterRoutes(RouteCollection routes)
{
    routes.DiscoverMvcControllerRoutes();
}

This DiscoverMvcControllerRoutes() method looks for UrlRoute attributes in our assembly. That assembly is discovered dynamically using the System.Reflection.Assembly.GetCallingAssembly() method.

The thing is, the documentation in the MSDN states the following:

If the method that calls the GetCallingAssembly method is expanded inline by the just-in-time (JIT) compiler, or if its caller is expanded inline, the assembly that is returned by GetCallingAssembly may differ unexpectedly.

The result is:

  • in DEBUG mode, GetCallingAssembly() returns the assembly which defines our controllers.
  • in RELEASE mode, the method returns the System.Web assembly which contains no controller!

GetCallingAssemblyAndInlining.png

A very simple workaround is to pass the assembly as a parameter to the method which discovers the attributes:

public static void RegisterRoutes(RouteCollection routes)
{
    routes.DiscoverMvcControllerRoutes(typeof(MvcApplication).Assembly, null);
}
Today, I have released version 0.12.1 of Hudson Tray Tracker. The main improvement of this release is the support for stuck builds. Instead of getting disturbed by regression warnings, you will now seen icons with a gray "H": HTT_StuckBuilds.png Here are the download links: The change log for this version is:
  • Fix (#52): added support for servers with an untrusted SSL certificate.
  • Fix (#56): no longer display regression warnings when a successful build follows an aborted build.
  • Fix (#58): better handling of user names containing dashes.
  • Feature (#61): servers can now be given a name which is shown in replacement of the URL.
  • Fix (#67): the stuck status is now properly handled (icons show a gray "H").
  • Fix (#68): no longer need to add the "http://" prefix when defining a server.
  • Other:
    • New icons in higher resolution.
    • Configuration is now stored in JSON.
The Spy Memcached client has its own logging layer abstraction. That's unfortunate as slf4j does the exact same job, allowing you to choose the concrete logger implementation easily.

To allow the Spy Memcached client to use log4j, you need to define the net.spy.log.LoggerImpl system property to the appropriate value:
-Dnet.spy.log.LoggerImpl=net.spy.log.Log4JLogger

Reference: spymemcached - Logging
I just came across this question on StackOverflow: trigger 404 in spring-mvc controller?

It shows a nice way to send other HTTP response codes than 200 in a Spring MVC controller. Here is an example:
@ResponseStatus(value = HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {
    ...
}

@Controller
public class SomeController {
    @RequestMapping.....
    public void handleCall() {
        if (isFound()) {
            // whatever
        }
        else {
            throw new ResourceNotFoundException(); 
        }
    }
}
Microsoft's document Customizing the File Types IIS Compresses (IIS 6.0) explains how to enable static file compression in IIS 6. This document might be confusing as you should not put quotes when setting the list of included file extensions. For instance, you should not write:
cscript adsutil.vbs SET W3SVC/Filters/Compression/Deflate/HcFileExtensions "htm html txt css js"
but:
cscript adsutil.vbs SET W3SVC/Filters/Compression/Deflate/HcFileExtensions htm html txt css js