HTML Solution
If all current browsers were HTML5-compatible, or if they at least supported the <audio> and <video> tags, our task would be really easy. At the very least, the latest versions of Firefox, Chrome, Safari, and Opera all provide support for those tags. But, as it usually happens, Microsoft's own Internet Explorer doesn't recognize these tags, so we're out of a common, modern, standard solution. Even if Internet Explorer 9 promises some support for the required tags, we must recognize the fact that there still are many Internet Explorer 6, 7, and 8 users, so we couldn't depend on everybody updating browsers just for the sake of our application's sound.
The first solution we'll want to try is using HTML tags, but then we'll have to work out different solutions, depending on the user's browser. We could make do by using just two tags: <audio> for most browsers, and <embed> for Internet Explorer. These two tags have some common characteristics, such as having a play() method, so we can have a common base class for both (see Listing 2).
Listing 2 The BrowserAudioElement class will be used for HTML-based solutions, such as using <audio> and <embed> tags.
package com.fkereki.multimedia.client; /** * This is the base class we'll be using for browser-defined audio * elements, such as HTML5's <audio> tag, or Internet Explorer's <embed> * tag. We are taking advantage here that both those objects have a * play() method; otherwise, we'd have to write separate definitions. * * For the play() method to work, we need a distinct id value for * the browser element, and we use audioCounter statically so each new * object will get a new id. Note the usage of JSNI in play(), to call * the (Java) generateId() method. */ public abstract class BrowserAudioElement extends AudioElement { static int audioCounter= 22960; int selfCounter= 0; protected String generateId() { return "audioelement" + selfCounter; } public BrowserAudioElement(final String audioUrl) { super(audioUrl); audioCounter++; selfCounter= audioCounter; } @Override public native void play() /*-{ id= this.@com.fkereki.multimedia.client.BrowserAudioElement::generateId()(); $doc.getElementById(id).play(); }-*/; }
This class requires some explanations. To play a sound, we must first add the corresponding element to the current page code, by using some DOM manipulations. However, if we want to be able to play() the sound, we need to be able to access the recently added element, which requires an ID of its own. We can generate distinct IDs by using the static audioCounter variable and assigning a new value to each created object. Then we can use the assigned value to create an ID via the generateId() method. (Of course, we could have directly created and stored the ID in an attribute; feel free to change the implementation in this way.)
Playing the sound will require getting the generated id value, using it to get the element and invoke its play() method; this is done by using a native (JavaScript) method through JSNI. The code has two details: The line starting id=... shows the somewhat weird way to call a Java method from within JavaScript code, and the $doc variable in the next line is used to get at the current document. (GWT code resides in a different frame, so you couldn't just use document as usual.)
With this class out of the way, let's get down to producing some sound. For HTML5-enabled browsers, we could work with a class such as Html5AudioElement (see Listing 3), which produces the barest minimum necessary HTML code. We create the <audio> element by using GWT's HTML class, and then we add it to the RootPanel so it will become a part of our page.
Listing 3 The very bare-bones Html5AudioElement class provides sound for HTML5-enabled browsers by using the <audio> tag.
package com.fkereki.multimedia.client; import com.google.gwt.user.client.ui.HTML; import com.google.gwt.user.client.ui.RootPanel; /** * This is a straightforward usage of the HTML5 <audio> tag. There are * many parameters that could be used, but for our limited objectives, * this is enough. * * See http://www.whatwg.org/specs/web-apps/current-work/#audio for more * on this tag. */ public class Html5AudioElement extends BrowserAudioElement { public Html5AudioElement(final String audioUrl) { super(audioUrl); final HTML audio1= new HTML("<audio id='" + generateId() + "' src='" + audioUrl + "' />"); RootPanel.get().add(audio1); } }
For Internet Explorer browsers, we would require a somewhat different, but quite similar, EmbedAudioElement class (see Listing 4). Notice that we set the height and width attributes so no controls will be seen onscreen.
Listing 4 A sound class based on <embed> for Internet Explorer browsers.
package com.fkereki.multimedia.client; import com.google.gwt.user.client.ui.HTML; import com.google.gwt.user.client.ui.RootPanel; /** * Using <embed> is a simple way to get audio in Internet Explorer. Note * that we need set the height and width attributes to zero, so the * object won't be visible. */ public class EmbedAudioElement extends BrowserAudioElement { public EmbedAudioElement(final String audioUrl) { super(audioUrl); final HTML audio1= new HTML("<embed id='" + generateId() + "' src='" + audioUrl + "' type='application/x-mplayer2' "+ "height='0' width='0' autostart='0' loop='0'></embed>"); RootPanel.get().add(audio1); } }
To test these methods, we could write something like the code in Listing 5. Later, we'll add other ways of playing sounds.
Listing 5 A sample program that plays a sound.
package com.fkereki.multimedia.client; import com.google.gwt.core.client.EntryPoint; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.user.client.ui.VerticalPanel; public class Multimedia implements EntryPoint { @Override public void onModuleLoad() { VerticalPanel vp= new VerticalPanel(); final Html5AudioElement audio1a= new Html5AudioElement("TYPING_t.wav"); final Button playAudioButton1a= new Button("Play Audio (Typing) "+ "Through HTML5 via JSNI"); vp.add(playAudioButton1a); playAudioButton1a.addClickHandler(new ClickHandler() { @Override public void onClick(final ClickEvent event) { audio1a.play(); } }); final EmbedAudioElement audio1b= new EmbedAudioElement("TYPING_t.wav"); final Button playAudioButton1b= new Button("Play Audio (Typing) "+ "Through Embedded object"); vp.add(playAudioButton1b); playAudioButton1b.addClickHandler(new ClickHandler() { @Override public void onClick(final ClickEvent event) { audio1b.play(); } }); // ...other ways of playing sounds... RootPanel.get().add(vp); } }
The format of the resulting page isn't fancy; we just provide several command buttons to test each possible sound implementation, as shown in Figure 1. (Notice that Figure 1 already includes a third way of playing a sound, which we'll study in a moment.) Of course, if the user clicks on the "wrong" button, he won't get any soundswe need to fix this problem!
Figure 1 A not-very-fancy window for testing different ways to get sound with GWT. Note that the first command button works only in HTML5-enabled browsers, and the second is geared to Internet Explorer only.