Adding the SourceElement for use with Audio and Video, and adding convenience methods in those widgets to use the element. Multiple SourceElements can be specified for an AudioElement/VideoElement widget, and the browser will choose and download one of the sources that it can play. This is convenient and easier than doing a runtime check in user code to figure out the best source file. Review at http://gwt-code-reviews.appspot.com/1423810 Review by: pdr@google.com git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@10117 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/samples/mobilewebapp/src/com/google/gwt/sample/mobilewebapp/client/ui/SoundEffects.java b/samples/mobilewebapp/src/com/google/gwt/sample/mobilewebapp/client/ui/SoundEffects.java index e3ed177..e690e42 100644 --- a/samples/mobilewebapp/src/com/google/gwt/sample/mobilewebapp/client/ui/SoundEffects.java +++ b/samples/mobilewebapp/src/com/google/gwt/sample/mobilewebapp/client/ui/SoundEffects.java
@@ -16,49 +16,13 @@ package com.google.gwt.sample.mobilewebapp.client.ui; import com.google.gwt.dom.client.AudioElement; -import com.google.gwt.dom.client.Document; import com.google.gwt.media.client.Audio; -import java.util.ArrayList; -import java.util.List; - /** * A bundle of sound effects that can be used in the application. */ public class SoundEffects { - /** - * The source path and type of an audio source file. - */ - private static class AudioSource { - private final String source; - private final AudioType type; - - public AudioSource(String source, AudioType type) { - this.source = source; - this.type = type; - } - } - - /** - * The supported audio types. - */ - private static enum AudioType { - OGG("audio/ogg"), MP3("audio/mp3"), WAV("audio/wav"); - - private final String mimeType; - - private AudioType(String mimeType) { - this.mimeType = mimeType; - } - - public String getMimeType() { - return mimeType; - } - } - - private static List<AudioType> typePreference = new ArrayList<AudioType>(); - private static SoundEffects instance; private static boolean isSupported; @@ -71,37 +35,11 @@ if (instance == null) { isSupported = Audio.isSupported(); instance = new SoundEffects(); - - // Detect which audio types we support. - if (isSupported) { - AudioElement elem = Document.get().createAudioElement(); - - // Prefer "can play probably" to "can play maybe". - for (AudioType audioType : AudioType.values()) { - if (AudioElement.CAN_PLAY_PROBABLY.equals(elem.canPlayType(audioType.getMimeType()))) { - typePreference.add(audioType); - } - } - - // Use "can play maybe" if its the only thing available. - for (AudioType audioType : AudioType.values()) { - if (AudioElement.CAN_PLAY_MAYBE.equals(elem.canPlayType(audioType.getMimeType()))) { - typePreference.add(audioType); - } - } - } } return instance; } - /** - * Create and return an audio source. - */ - private static AudioSource audioSource(String source, AudioType type) { - return new AudioSource(source, type); - } - - private AudioElement error; + private Audio error; /** * Construct using {@link #get()}. @@ -122,79 +60,43 @@ */ public void prefetchError() { if (isSupported && error == null) { - error = - createAudioElement(audioSource("audio/error.ogg", AudioType.OGG), audioSource( - "audio/error.mp3", AudioType.MP3), audioSource("audio/error.wav", AudioType.WAV)); + error = Audio.createIfSupported(); + error.addSource("audio/error.ogg", AudioElement.TYPE_OGG); + error.addSource("audio/error.mp3", AudioElement.TYPE_MP3); + error.addSource("audio/error.wav", AudioElement.TYPE_WAV); + prefetchAudio(error); } } /** - * Create an {@link Audio} that will play one of the specified source media - * files. The sources will be tried in the order they are added until a - * supported format is found. + * Play an audio. * - * <p> - * This method will attempt to prefetch the audio sources by playing the file - * muted. - * </p> - * - * @param sources the source files, of which one will be chosen - * @return a new {@link AudioElement}, or null if not supported + * @param audio the audio to play, or null if not supported */ - private AudioElement createAudioElement(AudioSource... sources) { - if (!isSupported) { - return null; - } - - AudioSource bestSource = null; - for (int i = 0; i < typePreference.size() && bestSource == null; i++) { - AudioType type = typePreference.get(i); - for (AudioSource source : sources) { - if (source.type == type) { - bestSource = source; - break; - } - } - } - - // None of the source files are supported. - if (bestSource == null) { - return null; - } - - // Create the audio element. - AudioElement audio = Document.get().createAudioElement(); - audio.setSrc(bestSource.source); - - // Force the browser to fetch the source files. - audio.setVolume(0.0); - audio.play(); - - return audio; - } - - /** - * Play an audio element. - * - * @param audio the audio element to play, or null if not supported - */ - private void playAudio(AudioElement audio) { + private void playAudio(Audio audio) { if (audio == null) { return; } - + // Pause current progress. audio.pause(); - - /* - * Some browsers throw an error when we try to seek back to time 0, so reset - * the source instead. The audio file should be loaded from the browser - * cache. - */ - audio.setSrc(audio.getSrc()); - + + // Reset the source. + // TODO(jlabanca): Is cache-control=private making the source unseekable? + audio.setSrc(audio.getCurrentSrc()); + // Unmute because we muted in createAudioElement. - audio.setVolume(1.0); audio.play(); } + + /** + * Prefetch an audio. + * + * @param audio the audio to prefetch, or null if not supported + */ + private void prefetchAudio(Audio audio) { + if (audio != null) { + audio.load(); + } + } }
diff --git a/user/src/com/google/gwt/dom/client/AudioElement.java b/user/src/com/google/gwt/dom/client/AudioElement.java index 2cf92e1..fdf8bff 100644 --- a/user/src/com/google/gwt/dom/client/AudioElement.java +++ b/user/src/com/google/gwt/dom/client/AudioElement.java
@@ -34,6 +34,21 @@ */ public static final String TAG = "audio"; + /** + * The audio type of MP3 encoded audio. + */ + public static final String TYPE_MP3 = "audio/mpeg"; + + /** + * The audio type of Ogg encoded audio. + */ + public static final String TYPE_OGG = "audio/ogg"; + + /** + * The audio type of WAV encoded audio. + */ + public static final String TYPE_WAV = "audio/wav"; + protected AudioElement() { } }
diff --git a/user/src/com/google/gwt/dom/client/Document.java b/user/src/com/google/gwt/dom/client/Document.java index ce96a90..39321eb 100644 --- a/user/src/com/google/gwt/dom/client/Document.java +++ b/user/src/com/google/gwt/dom/client/Document.java
@@ -1050,6 +1050,15 @@ } /** + * Creates an <source> element. + * + * @return the newly created element + */ + public final SourceElement createSourceElement() { + return (SourceElement) DOMImpl.impl.createElement(this, SourceElement.TAG); + } + + /** * Creates a <span> element. * * @return the newly created element
diff --git a/user/src/com/google/gwt/dom/client/SourceElement.java b/user/src/com/google/gwt/dom/client/SourceElement.java new file mode 100644 index 0000000..0f63ca9 --- /dev/null +++ b/user/src/com/google/gwt/dom/client/SourceElement.java
@@ -0,0 +1,100 @@ +/* + * Copyright 2011 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.gwt.dom.client; + +/** + * The SOURCE element specifies one of potentially multiple source file in a + * media element. + * + * @see <a href="http://www.w3.org/TR/html5/video.html#the-source-element">W3C + * HTML Specification</a> + */ +@TagName(SourceElement.TAG) +public class SourceElement extends Element { + + static final String TAG = "source"; + + /** + * Assert that the given {@link Element} is compatible with this class and + * automatically typecast it. + */ + public static SourceElement as(Element elem) { + assert elem.getTagName().equalsIgnoreCase(TAG); + return (SourceElement) elem; + } + + protected SourceElement() { + } + + /** + * Returns the source URL for the media, or {@code null} if none is set. + * + * @return a String URL or {@code null} + * + * @see #setSrc(String) + */ + public final native String getSrc() /*-{ + return this.src; + }-*/; + + /** + * Returns the type of media represented by the src, or {@code null} if none + * is set. + * + * @return a String type, or {@code null} + * + * @see #setType(String) + */ + public final native String getType() /*-{ + return this.type; + }-*/; + + /** + * Sets the source URL for the media. + * + * @param url a String URL + * + * @see #getSrc() + */ + public final native void setSrc(String url) /*-{ + this.src = url; + }-*/; + + /** + * Sets the type of media represented by the src. The browser will look at the + * type when deciding which source files to request from the server. + * + * <p> + * The type is the format or encoding of the media represented by the source + * element. For example, the type of an {@link AudioElement} could be one of + * {@value AudioElement#TYPE_OGG}, {@link AudioElement#TYPE_MP3}, or + * {@link AudioElement#TYPE_WAV}. + * </p> + * + * <p> + * You can also add the codec information to the type, giving the browser even + * more information about whether or not it can play the file (Example: " + * <code>audio/ogg; codec=vorbis</code>"); + * </p> + * + * @param type the media type + * + * @see #getType() + */ + public final native void setType(String type) /*-{ + this.type = type; + }-*/; +}
diff --git a/user/src/com/google/gwt/dom/client/VideoElement.java b/user/src/com/google/gwt/dom/client/VideoElement.java index 3fecf60..b01b219 100644 --- a/user/src/com/google/gwt/dom/client/VideoElement.java +++ b/user/src/com/google/gwt/dom/client/VideoElement.java
@@ -34,6 +34,21 @@ */ public static final String TAG = "video"; + /** + * The audio type of MP4 encoded video. + */ + public static final String TYPE_MP4 = "video/mp4"; + + /** + * The audio type of Ogg encoded video. + */ + public static final String TYPE_OGG = "video/ogg"; + + /** + * The audio type of WebM encoded audio. + */ + public static final String TYPE_WEBM = "video/webm"; + protected VideoElement() { }
diff --git a/user/src/com/google/gwt/media/client/MediaBase.java b/user/src/com/google/gwt/media/client/MediaBase.java index 3d0a1cb..3bca63a 100644 --- a/user/src/com/google/gwt/media/client/MediaBase.java +++ b/user/src/com/google/gwt/media/client/MediaBase.java
@@ -15,7 +15,9 @@ */ package com.google.gwt.media.client; +import com.google.gwt.dom.client.Document; import com.google.gwt.dom.client.MediaElement; +import com.google.gwt.dom.client.SourceElement; import com.google.gwt.event.dom.client.CanPlayThroughEvent; import com.google.gwt.event.dom.client.CanPlayThroughHandler; import com.google.gwt.event.dom.client.EndedEvent; @@ -61,6 +63,57 @@ } /** + * Add a source element to this media. The browser will request source files + * from the server until it finds one it can play. + * + * <p> + * Only use this method if you do not know the type of the source file, as the + * browser cannot determine the format from the filename and must download + * each source until a compatible one is found. Instead, you should specify + * the type for the media using {@link #addSource(String, String)} so the + * browser can choose a source file without downloading the file. + * </p> + * + * @param url a String URL + * @see #addSource(String, String) + */ + public SourceElement addSource(String url) { + SourceElement elem = Document.get().createSourceElement(); + elem.setSrc(url); + getElement().appendChild(elem); + return elem; + } + + /** + * Add a source element to this media, specifying the type (format) of the + * media. The browser will choose a supported source file and download it. + * + * <p> + * The type is the format or encoding of the media represented by the source + * element. For example, the type of an + * {@link com.google.gwt.dom.client.AudioElement} could be one of + * {@value com.google.gwt.dom.client.AudioElement#TYPE_OGG}, + * {@link com.google.gwt.dom.client.AudioElement#TYPE_MP3}, or + * {@link com.google.gwt.dom.client.AudioElement#TYPE_WAV}. + * </p> + * + * <p> + * You can also add the codec information to the type, giving the browser even + * more information about whether or not it can play the file (Example: " + * <code>audio/ogg; codec=vorbis</code>"); + * </p> + * + * @param url a String URL + * @param type the type (format) of the media + * @see #getSrc() + */ + public SourceElement addSource(String url, String type) { + SourceElement elem = addSource(url); + elem.setType(type); + return elem; + } + + /** * Returns {@code true} if the native player is capable of playing content of * the given MIME type. * @@ -384,6 +437,17 @@ } /** + * Remove the specified {@link SourceElement} from this media. If the source + * element is not a child of this widget, it will not be removed. + * + * @param source the source element to remove + * @see #addSource(String, String) + */ + public void removeSource(SourceElement source) { + getElement().removeChild(source); + } + + /** * Enables or disables autoplay of the resource. * * @param autoplay if {@code true}, enable autoplay @@ -479,10 +543,18 @@ /** * Sets the source URL for the media. - * + * + * <p> + * Support for different media types varies between browsers. Instead of using + * this method, you should encode your media in multiple formats and add all + * of them using {@link #addSource(String, String)} so the browser can choose + * a source that it supports. + * </p> + * * @param url a String URL - * + * * @see #getSrc() + * @see #addSource(String, String) */ public void setSrc(String url) { getMediaElement().setSrc(url);
diff --git a/user/test/com/google/gwt/media/client/MediaTest.java b/user/test/com/google/gwt/media/client/MediaTest.java index d288211..3997ae4 100644 --- a/user/test/com/google/gwt/media/client/MediaTest.java +++ b/user/test/com/google/gwt/media/client/MediaTest.java
@@ -16,6 +16,7 @@ package com.google.gwt.media.client; import com.google.gwt.dom.client.MediaElement; +import com.google.gwt.dom.client.SourceElement; import com.google.gwt.junit.DoNotRunWith; import com.google.gwt.junit.Platform; import com.google.gwt.junit.client.GWTTestCase; @@ -88,6 +89,30 @@ return "com.google.gwt.media.MediaTest"; } + public void testAddSource() { + final MediaBase media = getMedia(); + if (media == null) { + return; // don't continue if not supported + } + + // Add some source elements. + SourceElement source0 = media.addSource("file.ogg", "audio/ogg"); + assertEquals("file.ogg", source0.getSrc()); + assertEquals("audio/ogg", source0.getType()); + SourceElement source1 = media.addSource("file.ogv", "video/ogg"); + assertEquals("file.ogv", source1.getSrc()); + assertEquals("video/ogg", source1.getType()); + + // Add a source without a type. + SourceElement source2 = media.addSource("file.mp3"); + assertEquals("file.mp3", source2.getSrc()); + + // Check that the sources are a children of the media. + assertEquals(media.getElement(), source0.getParentElement()); + assertEquals(media.getElement(), source1.getParentElement()); + assertEquals(media.getElement(), source2.getParentElement()); + } + public void testAutoPlay() { final MediaBase media = getMedia(); if (media == null) { @@ -310,6 +335,36 @@ || state == MediaElement.HAVE_NOTHING); } + public void testRemoveSource() { + final MediaBase media = getMedia(); + if (media == null) { + return; // don't continue if not supported + } + + // Add some source elements. + SourceElement source0 = media.addSource("file.ogg", "audio/ogg"); + SourceElement source1 = media.addSource("file.ogv", "video/ogg"); + SourceElement source2 = media.addSource("file.mp3"); + assertEquals(media.getElement(), source0.getParentElement()); + assertEquals(media.getElement(), source1.getParentElement()); + assertEquals(media.getElement(), source2.getParentElement()); + + // Remove a source. + media.removeSource(source1); + assertEquals(media.getElement(), source0.getParentElement()); + assertNull(source1.getParentElement()); + assertEquals(media.getElement(), source2.getParentElement()); + + // Let a source remove itself. + source2.removeFromParent(); + assertEquals(media.getElement(), source0.getParentElement()); + assertNull(source1.getParentElement()); + assertNull(source2.getParentElement()); + + // Remove a source that is not a child. + media.removeSource(source0); + } + public void testSupported() { // test the isxxxSupported() call if running known sup or not sup browsers. if (isIE6()) {