Audio and Video cleanup.

Add a MediaBase widget backing Audio and Video, and clean up tests. Also,
address comments by reviewer and re-encode test media files for a smaller size
and to fix an h264 video issue.

Review at http://gwt-code-reviews.appspot.com/1415801


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@10001 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/tools/api-checker/config/gwt22_23userApi.conf b/tools/api-checker/config/gwt22_23userApi.conf
index 7041d0b..e1c2e38 100644
--- a/tools/api-checker/config/gwt22_23userApi.conf
+++ b/tools/api-checker/config/gwt22_23userApi.conf
@@ -123,3 +123,6 @@
 
 # Renamed CellBasedWidgetImplSafari to CellBasedWidgetImplStandardBase.
 com.google.gwt.user.cellview.client.CellBasedWidgetImplSafari MISSING
+
+# Adding protected Video(Element) that conflicts with existing (now deprecated) public Video(String)
+com.google.gwt.media.client.Video::Video(Ljava/lang/String;) OVERLOADED_METHOD_CALL
diff --git a/user/src/com/google/gwt/dom/client/DOMImplMozilla.java b/user/src/com/google/gwt/dom/client/DOMImplMozilla.java
index fc68ef6..bced2ef 100644
--- a/user/src/com/google/gwt/dom/client/DOMImplMozilla.java
+++ b/user/src/com/google/gwt/dom/client/DOMImplMozilla.java
@@ -250,14 +250,8 @@
    * @return true if using Gecko 1.9 (Firefox 3) or later
    */
   private native boolean isGecko19() /*-{
-    var result = /rv:([0-9]+)\.([0-9]+)/.exec(navigator.userAgent.toLowerCase());
-    if (result && result.length == 3) {
-      var version = (parseInt(result[1]) * 1000) + parseInt(result[2]);
-      if (version >= 1009) {
-        return true;
-      }
-    }
-    return false;
+    var geckoVersion = @com.google.gwt.dom.client.DOMImplMozilla::getGeckoVersion()();
+    return (geckoVersion != -1) && (geckoVersion >= 1009000);
   }-*/;
 
   private native boolean isRTL(Element elem) /*-{
diff --git a/user/src/com/google/gwt/media/client/Audio.java b/user/src/com/google/gwt/media/client/Audio.java
index 4152298..f5100f7 100644
--- a/user/src/com/google/gwt/media/client/Audio.java
+++ b/user/src/com/google/gwt/media/client/Audio.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -19,126 +19,20 @@
 import com.google.gwt.dom.client.AudioElement;
 import com.google.gwt.dom.client.Document;
 import com.google.gwt.dom.client.PartialSupport;
-import com.google.gwt.event.dom.client.CanPlayThroughEvent;
-import com.google.gwt.event.dom.client.CanPlayThroughHandler;
-import com.google.gwt.event.dom.client.EndedEvent;
-import com.google.gwt.event.dom.client.EndedHandler;
-import com.google.gwt.event.dom.client.HasAllMediaHandlers;
-import com.google.gwt.event.dom.client.ProgressEvent;
-import com.google.gwt.event.dom.client.ProgressHandler;
-import com.google.gwt.event.shared.HandlerRegistration;
-import com.google.gwt.user.client.ui.FocusWidget;
 
 /**
  * <p>
  * A widget representing an &lt;audio&gt; element.
- * 
+ *
  * <p>
  * <span style="color:red">Experimental API: This API is still under development
- * and is subject to change.
- * </span>
+ * and is subject to change. </span>
  * </p>
- * 
+ *
  * This widget may not be supported on all browsers.
  */
 @PartialSupport
-public class Audio extends FocusWidget implements HasAllMediaHandlers {
-  private static AudioElementSupportDetector detector;
-
-  /**
-   * Return a new {@link Audio} if supported,  and null otherwise.
-   * 
-   * @return a new {@link Audio} if supported, and null otherwise
-   */
-  public static Audio createIfSupported() {
-    if (detector == null) {
-      detector = GWT.create(AudioElementSupportDetector.class);
-    }
-    if (!detector.isSupportedCompileTime()) {
-      return null;
-    }
-    AudioElement element = Document.get().createAudioElement();
-    if (!detector.isSupportedRunTime(element)) {
-      return null;
-    }
-    return new Audio(element);
-  }
-
-  /**
-   * Runtime check for whether the audio element is supported in this browser.
-   * 
-   * @return whether the audio element is supported
-   */
-  public static boolean isSupported() {
-    if (detector == null) {
-      detector = GWT.create(AudioElementSupportDetector.class);
-    }
-    if (!detector.isSupportedCompileTime()) {
-      return false;
-    }
-    AudioElement element = Document.get().createAudioElement();
-    if (!detector.isSupportedRunTime(element)) {
-      return false;
-    }
-    return true;
-  }
-
-  /**
-   * Protected constructor. Use {@link #createIfSupported()} to create 
-   * an Audio element.
-   */
-  private Audio(AudioElement element) {
-    setElement(element);
-  }
-
-  public HandlerRegistration addCanPlayThroughHandler(CanPlayThroughHandler handler) {
-    return addDomHandler(handler, CanPlayThroughEvent.getType());
-  }
-
-  public HandlerRegistration addEndedHandler(EndedHandler handler) {
-    return addDomHandler(handler, EndedEvent.getType());
-  }
-
-  public HandlerRegistration addProgressHandler(ProgressHandler handler) {
-    return addDomHandler(handler, ProgressEvent.getType());
-  }
-
-  /**
-   * Returns the attached AudioElement.
-   * 
-   * @return the AudioElement
-   */
-  public AudioElement getAudioElement() {
-    return this.getElement().cast();
-  }
-
-  /**
-   * Detector for browser support of {@link AudioElement}.
-   */
-  private static class AudioElementSupportDetector {
-    /**
-     * Using a run-time check, return true if the {@link AudioElement} is 
-     * supported.
-     * 
-     * @return true if supported, false otherwise.
-     */
-    static native boolean isSupportedRunTime(AudioElement element) /*-{
-      return !!element.play;
-    }-*/;
-
-    /**
-     * Using a compile-time check, return true if {@link AudioElement} might 
-     * be supported.
-     * 
-     * @return true if might be supported, false otherwise.
-     */
-    boolean isSupportedCompileTime() {
-      // will be true in AudioElementSupportDetectedMaybe
-      // will be false in AudioElementSupportDetectedNo
-      return false;
-    }
-  }
-
+public class Audio extends MediaBase {
   /**
    * Detector for permutations that might support {@link AudioElement}.
    */
@@ -172,6 +66,89 @@
     @Override
     boolean isSupportedCompileTime() {
       return false;
-    } 
+    }
+  }
+
+  /**
+   * Detector for browser support of {@link AudioElement}.
+   */
+  private static class AudioElementSupportDetector {
+    /**
+     * Using a run-time check, return true if the {@link AudioElement} is
+     * supported.
+     *
+     * @return true if supported, false otherwise.
+     */
+    static native boolean isSupportedRunTime(AudioElement element) /*-{
+      return !!element.play;
+    }-*/;
+
+    /**
+     * Using a compile-time check, return true if {@link AudioElement} might be
+     * supported.
+     *
+     * @return true if might be supported, false otherwise.
+     */
+    boolean isSupportedCompileTime() {
+      // will be true in AudioElementSupportDetectedMaybe
+      // will be false in AudioElementSupportDetectedNo
+      return false;
+    }
+  }
+
+  private static AudioElementSupportDetector detector;
+
+  /**
+   * Return a new {@link Audio} if supported, and null otherwise.
+   *
+   * @return a new {@link Audio} if supported, and null otherwise
+   */
+  public static Audio createIfSupported() {
+    if (detector == null) {
+      detector = GWT.create(AudioElementSupportDetector.class);
+    }
+    if (!detector.isSupportedCompileTime()) {
+      return null;
+    }
+    AudioElement element = Document.get().createAudioElement();
+    if (!detector.isSupportedRunTime(element)) {
+      return null;
+    }
+    return new Audio(element);
+  }
+
+  /**
+   * Runtime check for whether the audio element is supported in this browser.
+   *
+   * @return whether the audio element is supported
+   */
+  public static boolean isSupported() {
+    if (detector == null) {
+      detector = GWT.create(AudioElementSupportDetector.class);
+    }
+    if (!detector.isSupportedCompileTime()) {
+      return false;
+    }
+    AudioElement element = Document.get().createAudioElement();
+    if (!detector.isSupportedRunTime(element)) {
+      return false;
+    }
+    return true;
+  }
+
+  /**
+   * Protected constructor. Use {@link #createIfSupported()} to create an Audio.
+   */
+  protected Audio(AudioElement element) {
+    super(element);
+  }
+
+  /**
+   * Returns the attached AudioElement.
+   *
+   * @return the AudioElement
+   */
+  public AudioElement getAudioElement() {
+    return getMediaElement().cast();
   }
 }
diff --git a/user/src/com/google/gwt/media/client/MediaBase.java b/user/src/com/google/gwt/media/client/MediaBase.java
new file mode 100644
index 0000000..3d0a1cb
--- /dev/null
+++ b/user/src/com/google/gwt/media/client/MediaBase.java
@@ -0,0 +1,501 @@
+/*
+ * 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.media.client;
+
+import com.google.gwt.dom.client.MediaElement;
+import com.google.gwt.event.dom.client.CanPlayThroughEvent;
+import com.google.gwt.event.dom.client.CanPlayThroughHandler;
+import com.google.gwt.event.dom.client.EndedEvent;
+import com.google.gwt.event.dom.client.EndedHandler;
+import com.google.gwt.event.dom.client.HasAllMediaHandlers;
+import com.google.gwt.event.dom.client.ProgressEvent;
+import com.google.gwt.event.dom.client.ProgressHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.media.dom.client.MediaError;
+import com.google.gwt.media.dom.client.TimeRanges;
+import com.google.gwt.user.client.ui.FocusWidget;
+
+/**
+ * <p>
+ * A widget representing a media element.
+ *
+ * <p>
+ * <span style="color:red">Experimental API: This API is still under development
+ * and is subject to change. </span>
+ * </p>
+ */
+public abstract class MediaBase extends FocusWidget
+    implements HasAllMediaHandlers {
+
+  /**
+   * Protected constructor.
+   */
+  protected MediaBase(MediaElement element) {
+    setElement(element);
+  }
+
+  public HandlerRegistration addCanPlayThroughHandler(
+      CanPlayThroughHandler handler) {
+    return addDomHandler(handler, CanPlayThroughEvent.getType());
+  }
+
+  public HandlerRegistration addEndedHandler(EndedHandler handler) {
+    return addDomHandler(handler, EndedEvent.getType());
+  }
+
+  public HandlerRegistration addProgressHandler(ProgressHandler handler) {
+    return addDomHandler(handler, ProgressEvent.getType());
+  }
+
+  /**
+   * Returns {@code true} if the native player is capable of playing content of
+   * the given MIME type.
+   *
+   * @param type a String representing a MIME type
+   * @return one of {@link MediaElement#CAN_PLAY_PROBABLY},
+   *         {@link MediaElement#CAN_PLAY_MAYBE}, or
+   *         {@link MediaElement#CANNOT_PLAY}
+   */
+  public String canPlayType(String type) {
+    return getMediaElement().canPlayType(type);
+  }
+
+  /**
+   * Returns a {@link TimeRanges} object indicating which portions of the source
+   * have been buffered locally.
+   *
+   * @return a {@link TimeRanges} instance, or {@code null}.
+   */
+  public TimeRanges getBuffered() {
+    return getMediaElement().getBuffered();
+  }
+
+  /**
+   * Returns the URL of the current media source, or the empty String if no
+   * source is set.
+   *
+   * @return a String URL
+   */
+  public String getCurrentSrc() {
+    return getMediaElement().getCurrentSrc();
+  }
+
+  /**
+   * Returns the current time within the source media stream.
+   *
+   * @return the time, in seconds, as a double
+   *
+   * @see #setCurrentTime(double)
+   */
+  public double getCurrentTime() {
+    return getMediaElement().getCurrentTime();
+  }
+
+  /**
+   * Returns the default playback rate, where 1.0 corresponds to normal
+   * playback. If no rate has been set, 1.0 is returned.
+   *
+   * @return the current default playback rate, or 1.0 if it has not been set
+   *
+   * @see #setDefaultPlaybackRate(double)
+   */
+  public double getDefaultPlaybackRate() {
+    return getMediaElement().getDefaultPlaybackRate();
+  }
+
+  /**
+   * Returns the duration of the source media stream, in seconds. If the
+   * duration is unknown, {@link Double#NaN} is returned. For unbounded media
+   * streams, {@link Double#POSITIVE_INFINITY} is returned.
+   *
+   * @return a positive duration in seconds, NaN, or Infinity
+   */
+  public double getDuration() {
+    return getMediaElement().getDuration();
+  }
+
+  /**
+   * Returns the type of error that has occurred while attempting to load and
+   * play the media. If no error has occurred, {@code null} is returned.
+   *
+   * @return a {@link MediaError} instance, or {@code null}
+   */
+  public MediaError getError() {
+    return getMediaElement().getError();
+  }
+
+  /**
+   * Returns the time to which the media stream was seeked at the time it was
+   * loaded, in seconds, or 0.0 if the position is unknown.
+   *
+   * @return the initial time, or 0.0 if unknown
+   */
+  public double getInitialTime() {
+    return getMediaElement().getInitialTime();
+  }
+
+  /**
+   * Returns the attached Media Element.
+   *
+   * @return the Media Element
+   */
+  public MediaElement getMediaElement() {
+    return this.getElement().cast();
+  }
+
+  /**
+   * Returns the network state, one of {@link MediaElement#NETWORK_EMPTY},
+   * {@link MediaElement#NETWORK_IDLE}, {@link MediaElement#NETWORK_LOADING}, or
+   * {@link MediaElement#NETWORK_NO_SOURCE}.
+   *
+   * @return an integer constant indicating the network state
+   *
+   * @see MediaElement#NETWORK_EMPTY
+   * @see MediaElement#NETWORK_IDLE
+   * @see MediaElement#NETWORK_LOADING
+   * @see MediaElement#NETWORK_NO_SOURCE
+   */
+  public int getNetworkState() {
+    return getMediaElement().getNetworkState();
+  }
+
+  /**
+   * Returns the playback rate, where 1.0 corresponds to normal playback. If the
+   * rate has not been set, 1.0 is returned.
+   *
+   * @return the playback rate, if known, otherwise 1.0
+   *
+   * @see #setPlaybackRate(double)
+   */
+  public double getPlaybackRate() {
+    return getMediaElement().getPlaybackRate();
+  }
+
+  /**
+   * Returns a {@link TimeRanges} object indicating which portions of the source
+   * have been played.
+   *
+   * @return a {@link TimeRanges} instance, or {@code null}.
+   */
+  public TimeRanges getPlayed() {
+    return getMediaElement().getPlayed();
+  }
+
+  /**
+   * Returns the preload setting, one of {@link MediaElement#PRELOAD_AUTO},
+   * {@link MediaElement#PRELOAD_METADATA}, or
+   * {@link MediaElement#PRELOAD_NONE}.
+   *
+   * @return the preload setting
+   *
+   * @see #setPreload(String)
+   * @see MediaElement#PRELOAD_AUTO
+   * @see MediaElement#PRELOAD_METADATA
+   * @see MediaElement#PRELOAD_NONE
+   */
+  public String getPreload() {
+    return getMediaElement().getPreload();
+  }
+
+  /**
+   * Returns the current state of the media with respect to rendering the
+   * current playback position, as one of the constants
+   * {@link MediaElement#HAVE_CURRENT_DATA},
+   * {@link MediaElement#HAVE_ENOUGH_DATA},
+   * {@link MediaElement#HAVE_FUTURE_DATA}, {@link MediaElement#HAVE_METADATA},
+   * or {@link MediaElement#HAVE_NOTHING} .
+   *
+   * @return an integer constant indicating the ready state
+   *
+   * @see MediaElement#HAVE_CURRENT_DATA
+   * @see MediaElement#HAVE_ENOUGH_DATA
+   * @see MediaElement#HAVE_FUTURE_DATA
+   * @see MediaElement#HAVE_METADATA
+   * @see MediaElement#HAVE_NOTHING
+   */
+  public int getReadyState() {
+    return getMediaElement().getReadyState();
+  }
+
+  /**
+   * Returns a {@link TimeRanges} object indicating which portions of the source
+   * are seekable.
+   *
+   * @return a {@link TimeRanges} instance, or {@code null}.
+   */
+  public TimeRanges getSeekable() {
+    return getMediaElement().getSeekable();
+  }
+
+  /**
+   * 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 String getSrc() {
+    return getMediaElement().getSrc();
+  }
+
+  /**
+   * Returns the time corresponding to the zero time in the media timeline,
+   * measured in seconds since midnight, January 1 1970 UTC, or
+   * {@link Double#NaN} if none is specified.
+   *
+   * @return the start time
+   */
+  public double getStartOffsetTime() {
+    return getMediaElement().getStartOffsetTime();
+  }
+
+  /**
+   * Returns the current audio volume setting for the media, as a number between
+   * 0.0 and 1.0.
+   *
+   * @return a number between 0.0 (silent) and 1.0 (loudest)
+   *
+   * @see #setVolume(double)
+   */
+  public double getVolume() {
+    return getMediaElement().getVolume();
+  }
+
+  /**
+   * Returns {@code true} if the media player should display interactive
+   * controls (for example, to control play/pause, seek position, and volume),
+   * {@code false} otherwise.
+   *
+   * @return whether controls should be displayed
+   *
+   * @see #setControls(boolean)
+   */
+  public boolean hasControls() {
+    return getMediaElement().hasControls();
+  }
+
+  /**
+   * Returns {@code true} if playback has reached the end of the media, {@code
+   * false} otherwise.
+   *
+   * @return whether playback has ended
+   */
+  public boolean hasEnded() {
+    return getMediaElement().hasEnded();
+  }
+
+  /**
+   * Returns {@code true} if autoplay is enabled, {@code false} otherwise. When
+   * autoplay is enabled, the user agent will begin playback automatically as
+   * soon as it can do so without stopping.
+   *
+   * @return the autoplay setting
+   *
+   * @see #setAutoplay(boolean)
+   */
+  public boolean isAutoplay() {
+    return getMediaElement().isAutoplay();
+  }
+
+  /**
+   * Returns {@code true} if the user agent is to seek back to the start of the
+   * media once playing has ended, {@code false} otherwise.
+   *
+   * @return the loop setting
+   *
+   * @see #setLoop(boolean)
+   */
+  public boolean isLoop() {
+    return getMediaElement().isLoop();
+  }
+
+  /**
+   * Returns {@code true} if the volume is to be muted (overriding the normal
+   * volume setting), {@code false} otherwise.
+   *
+   * @return the muting setting
+   *
+   * @see #setMuted(boolean)
+   * @see #getVolume()
+   * @see #setVolume(double)
+   */
+  public boolean isMuted() {
+    return getMediaElement().isMuted();
+  }
+
+  /**
+   * Returns {@code true} if playback is paused, {@code false} otherwise.
+   *
+   * @return the paused setting
+   *
+   * @see #pause()
+   * @see #play()
+   */
+  public boolean isPaused() {
+    return getMediaElement().isPaused();
+  }
+
+  /**
+   * Returns {@code true} if the playback position is in the process of changing
+   * discontinuously, e.g., by use of the interactive controls, {@code false}
+   * otherwise.
+   *
+   * @return the seeking status
+   *
+   * @see #setControls(boolean)
+   * @see #hasControls()
+   */
+  public boolean isSeeking() {
+    return getMediaElement().isSeeking();
+  }
+
+  /**
+   * Causes the resource to be loaded.
+   */
+  public void load() {
+    getMediaElement().load();
+  }
+
+  /**
+   * Causes playback of the resource to be paused.
+   */
+  public void pause() {
+    getMediaElement().pause();
+  }
+
+  /**
+   * Causes playback of the resource to be started or resumed.
+   */
+  public void play() {
+    getMediaElement().play();
+  }
+
+  /**
+   * Enables or disables autoplay of the resource.
+   *
+   * @param autoplay if {@code true}, enable autoplay
+   *
+   * @see #isAutoplay()
+   */
+  public void setAutoplay(boolean autoplay) {
+    getMediaElement().setAutoplay(autoplay);
+  }
+
+  /**
+   * Enables or disables interactive controls.
+   *
+   * @param controls if {@code true}, enable controls
+   *
+   * @see #hasControls()
+   */
+  public void setControls(boolean controls) {
+    getMediaElement().setControls(controls);
+  }
+
+  /**
+   * Sets the current playback time within the media stream, in seconds.
+   *
+   * @param time a number within the ranges given by {@link #getSeekable()}
+   *
+   * @see #getCurrentTime()
+   */
+  public void setCurrentTime(double time) {
+    getMediaElement().setCurrentTime(time);
+  }
+
+  /**
+   * Sets the default playback rate.
+   *
+   * @param rate a double value
+   *
+   * @see #getDefaultPlaybackRate()
+   */
+  public void setDefaultPlaybackRate(double rate) {
+    getMediaElement().setDefaultPlaybackRate(rate);
+  }
+
+  /**
+   * Enables or disables looping.
+   *
+   * @param loop if {@code true}, enable looping
+   *
+   * @see #isLoop()
+   */
+  public final void setLoop(boolean loop) {
+    getMediaElement().setLoop(loop);
+  }
+
+  /**
+   * Enables or disables muting.
+   *
+   * @param muted if {@code true}, enable muting
+   *
+   * @see #isMuted()
+   */
+  public void setMuted(boolean muted) {
+    getMediaElement().setMuted(muted);
+  }
+
+  /**
+   * Sets the playback rate.
+   *
+   * @param rate a double value
+   *
+   * @see #getPlaybackRate()
+   */
+  public void setPlaybackRate(double rate) {
+    getMediaElement().setPlaybackRate(rate);
+  }
+
+  /**
+   * Changes the preload setting to one of {@link MediaElement#PRELOAD_AUTO},
+   * {@link MediaElement#PRELOAD_METADATA}, or
+   * {@link MediaElement#PRELOAD_NONE}.
+   *
+   * @param preload a String constants
+   *
+   * @see #getPreload()
+   * @see #setPreload(String)
+   * @see MediaElement#PRELOAD_AUTO
+   * @see MediaElement#PRELOAD_METADATA
+   * @see MediaElement#PRELOAD_NONE
+   */
+  public void setPreload(String preload) {
+    getMediaElement().setPreload(preload);
+  }
+
+  /**
+   * Sets the source URL for the media.
+   *
+   * @param url a String URL
+   *
+   * @see #getSrc()
+   */
+  public void setSrc(String url) {
+    getMediaElement().setSrc(url);
+  }
+
+  /**
+   * Sets the playback volume.
+   *
+   * @param volume a value between 0.0 (silent) and 1.0 (loudest)
+   *
+   * @see #getVolume()
+   */
+  public void setVolume(double volume) {
+    getMediaElement().setVolume(volume);
+  }
+}
diff --git a/user/src/com/google/gwt/media/client/Video.java b/user/src/com/google/gwt/media/client/Video.java
index 74eb17d..f3dcd8a 100644
--- a/user/src/com/google/gwt/media/client/Video.java
+++ b/user/src/com/google/gwt/media/client/Video.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -18,134 +18,19 @@
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.dom.client.Document;
 import com.google.gwt.dom.client.VideoElement;
-import com.google.gwt.event.dom.client.CanPlayThroughEvent;
-import com.google.gwt.event.dom.client.CanPlayThroughHandler;
-import com.google.gwt.event.dom.client.EndedEvent;
-import com.google.gwt.event.dom.client.EndedHandler;
-import com.google.gwt.event.dom.client.HasAllMediaHandlers;
-import com.google.gwt.event.dom.client.ProgressEvent;
-import com.google.gwt.event.dom.client.ProgressHandler;
-import com.google.gwt.event.shared.HandlerRegistration;
-import com.google.gwt.user.client.ui.FocusWidget;
 
 /**
  * <p>
  * A widget representing a &lt;video&gt; element.
- * 
+ *
  * <p>
  * <span style="color:red">Experimental API: This API is still under development
- * and is subject to change.
- * </span>
+ * and is subject to change. </span>
  * </p>
- * 
+ *
  * This widget may not be supported on all browsers.
  */
-public class Video extends FocusWidget implements HasAllMediaHandlers {
-  private static VideoElementSupportDetector detector;
-
-  /**
-   * Return a new {@link Video} if supported,  and null otherwise.
-   * 
-   * @return a new {@link Video} if supported, and null otherwise
-   */
-  public static Video createIfSupported() {
-    if (detector == null) {
-      detector = GWT.create(VideoElementSupportDetector.class);
-    }
-    if (!detector.isSupportedCompileTime()) {
-      return null;
-    }
-    VideoElement element = Document.get().createVideoElement();
-    if (!detector.isSupportedRunTime(element)) {
-      return null;
-    }
-    return new Video(element);
-  }
-
-  /**
-   * Runtime check for whether the video element is supported in this browser.
-   * 
-   * @return whether the video element is supported
-   */
-  public static boolean isSupported() {
-    if (detector == null) {
-      detector = GWT.create(VideoElementSupportDetector.class);
-    }
-    if (!detector.isSupportedCompileTime()) {
-      return false;
-    }
-    VideoElement element = Document.get().createVideoElement();
-    if (!detector.isSupportedRunTime(element)) {
-      return false;
-    }
-    return true;
-  }
-
-  /**
-   * Protected constructor. Use {@link #createIfSupported()} to create a Video.
-   */
-  private Video(VideoElement element) {
-    setElement(element);
-  }
-
-  /**
-   * Creates a Video widget with a given source URL.
-   * 
-   * @param src a String URL
-   */
-  public Video(String src) {
-    setElement(Document.get().createVideoElement());
-    getVideoElement().setSrc(src);
-  }
-
-  public HandlerRegistration addCanPlayThroughHandler(CanPlayThroughHandler handler) {
-    return addDomHandler(handler, CanPlayThroughEvent.getType());
-  }
-
-  public HandlerRegistration addEndedHandler(EndedHandler handler) {
-    return addDomHandler(handler, EndedEvent.getType());
-  }
-
-  public HandlerRegistration addProgressHandler(ProgressHandler handler) {
-    return addDomHandler(handler, ProgressEvent.getType());
-  }
-
-  /**
-   * Returns the attached VideoElement.
-   * 
-   * @return the VideoElement
-   */
-  public VideoElement getVideoElement() {
-    return this.getElement().cast();
-  }
-
-  /**
-   * Detector for browser support of {@link VideoElement}.
-   */
-  private static class VideoElementSupportDetector {
-    /**
-     * Using a run-time check, return true if the {@link VideoElement} is 
-     * supported.
-     * 
-     * @return true if supported, false otherwise.
-     */
-    static native boolean isSupportedRunTime(VideoElement element) /*-{
-      return !!element.play;
-    }-*/;
-
-    /**
-     * Using a compile-time check, return true if {@link VideoElement} might 
-     * be supported.
-     * 
-     * @return true if might be supported, false otherwise.
-     */
-    boolean isSupportedCompileTime() {
-      // will be true in VideoElementSupportDetectedMaybe
-      // will be false in VideoElementSupportDetectedNo
-      return false;
-    }
-  }
-
+public class Video extends MediaBase {
   /**
    * Detector for permutations that might support {@link VideoElement}.
    */
@@ -179,6 +64,144 @@
     @Override
     boolean isSupportedCompileTime() {
       return false;
-    } 
+    }
+  }
+
+  /**
+   * Detector for browser support of {@link VideoElement}.
+   */
+  private static class VideoElementSupportDetector {
+    /**
+     * Using a run-time check, return true if the {@link VideoElement} is
+     * supported.
+     *
+     * @return true if supported, false otherwise.
+     */
+    static native boolean isSupportedRunTime(VideoElement element) /*-{
+      return !!element.play;
+    }-*/;
+
+    /**
+     * Using a compile-time check, return true if {@link VideoElement} might be
+     * supported.
+     *
+     * @return true if might be supported, false otherwise.
+     */
+    boolean isSupportedCompileTime() {
+      // will be true in VideoElementSupportDetectedMaybe
+      // will be false in VideoElementSupportDetectedNo
+      return false;
+    }
+  }
+
+  private static VideoElementSupportDetector detector;
+
+  /**
+   * Return a new {@link Video} if supported, and null otherwise.
+   *
+   * @return a new {@link Video} if supported, and null otherwise
+   */
+  public static Video createIfSupported() {
+    if (detector == null) {
+      detector = GWT.create(VideoElementSupportDetector.class);
+    }
+    if (!detector.isSupportedCompileTime()) {
+      return null;
+    }
+    VideoElement element = Document.get().createVideoElement();
+    if (!detector.isSupportedRunTime(element)) {
+      return null;
+    }
+    return new Video(element);
+  }
+
+  /**
+   * Runtime check for whether the video element is supported in this browser.
+   *
+   * @return whether the video element is supported
+   */
+  public static boolean isSupported() {
+    if (detector == null) {
+      detector = GWT.create(VideoElementSupportDetector.class);
+    }
+    if (!detector.isSupportedCompileTime()) {
+      return false;
+    }
+    VideoElement element = Document.get().createVideoElement();
+    if (!detector.isSupportedRunTime(element)) {
+      return false;
+    }
+    return true;
+  }
+
+  /**
+   * Protected constructor. Use {@link #createIfSupported()} to create a Video.
+   */
+  protected Video(VideoElement element) {
+    super(element);
+  }
+
+  /**
+   * Creates a Video widget with a given source URL.
+   * 
+   * @param src a String URL.
+   * @deprecated use {@link #createIfSupported()}.
+   */
+  @Deprecated
+  public Video(String src) {
+    super(Document.get().createVideoElement());
+    getMediaElement().setSrc(src);
+  }
+
+  /**
+   * Returns a poster URL.
+   *
+   * @return a URL containing a poster image
+   *
+   * @see #setPoster(String)
+   */
+  public String getPoster() {
+    return getVideoElement().getPoster();
+  }
+
+  /**
+   * Returns the attached VideoElement.
+   *
+   * @return the VideoElement
+   */
+  public VideoElement getVideoElement() {
+    return getMediaElement().cast();
+  }
+
+  /**
+   * Gets the intrinsic height of video within the element.
+   *
+   * To get the element height, use {@link VideoElement#getOffsetHeight()}
+   *
+   * @return the height, in pixels
+   */
+  public int getVideoHeight() {
+    return getVideoElement().getVideoHeight();
+  }
+
+  /**
+   * Gets the instrinsic width of the video within the element.
+   *
+   * To get the element width, use {@link VideoElement#getOffsetWidth()}
+   *
+   * @return the width, in pixels
+   */
+  public int getVideoWidth() {
+    return getVideoElement().getVideoWidth();
+  }
+
+  /**
+   * Sets the poster URL.
+   *
+   * @param url the poster image URL
+   * @see #getPoster
+   */
+  public void setPoster(String url) {
+    getVideoElement().setPoster(url);
   }
 }
diff --git a/user/test/com/google/gwt/canvas/client/CanvasTest.java b/user/test/com/google/gwt/canvas/client/CanvasTest.java
index 8b3958a..d8b38d7 100644
--- a/user/test/com/google/gwt/canvas/client/CanvasTest.java
+++ b/user/test/com/google/gwt/canvas/client/CanvasTest.java
@@ -23,49 +23,36 @@
 
 /**
  * Tests {@link Canvas}.
- * 
- * Because HtmlUnit does not support HTML5, you will need to run these tests manually in order to
- * have them run. To do that, go to "run configurations" or "debug configurations", select the test
- * you would like to run, and put this line in the VM args under the arguments tab:
- * -Dgwt.args="-runStyle Manual:1"
+ *
+ *  Because HtmlUnit does not support HTML5, you will need to run these tests
+ * manually in order to have them run. To do that, go to "run configurations" or
+ * "debug configurations", select the test you would like to run, and put this
+ * line in the VM args under the arguments tab: -Dgwt.args="-runStyle Manual:1"
  */
 @DoNotRunWith(Platform.HtmlUnitUnknown)
 public class CanvasTest extends GWTTestCase {
-  protected Canvas canvas1;
-  protected Canvas canvas2;
+  private static native boolean isFirefox35OrLater() /*-{
+    var geckoVersion = @com.google.gwt.dom.client.DOMImplMozilla::getGeckoVersion()();
+    return (geckoVersion != -1) && (geckoVersion >= 1009001);
+  }-*/;
 
-  native boolean isWebkit525OrBefore() /*-{
+  private static native boolean isIE6() /*-{
+    return @com.google.gwt.dom.client.DOMImplIE6::isIE6()();
+  }-*/;
+
+  private static native boolean isWebkit525OrBefore() /*-{
     return @com.google.gwt.dom.client.DOMImplWebkit::isWebkit525OrBefore()();
   }-*/;
 
+  protected Canvas canvas1;
+
+  protected Canvas canvas2;
+
   @Override
   public String getModuleName() {
     return "com.google.gwt.canvas.Canvas";
   }
 
-  @Override
-  protected void gwtSetUp() throws Exception {
-    canvas1 = Canvas.createIfSupported();
-    canvas2 = Canvas.createIfSupported();
-    
-    if (canvas1 == null) {
-      return; // don't continue if not supported
-    }
-
-    RootPanel.get().add(canvas1);
-    RootPanel.get().add(canvas2);
-  }
-
-  @Override
-  protected void gwtTearDown() throws Exception {
-    if (canvas1 == null) {
-      return; // don't continue if not supported
-    }
-    
-    RootPanel.get().remove(canvas1);
-    RootPanel.get().remove(canvas2);
-  }
-
   /*
    * If the canvas has no pixels (i.e. either its horizontal dimension or its
    * vertical dimension is zero) then the method must return the string
@@ -91,7 +78,7 @@
     assertEquals(0, canvas1.getOffsetWidth());
     canvas1.setCoordinateSpaceHeight(0);
     canvas1.setCoordinateSpaceWidth(0);
-    
+
     String dataUrl = canvas1.toDataUrl();
     assertTrue("toDataURL() should return data:something",
         dataUrl.startsWith("data:"));
@@ -112,9 +99,9 @@
     canvas1.setWidth("10px");
     canvas1.setCoordinateSpaceHeight(10);
     canvas1.setCoordinateSpaceWidth(10);
-    
+
     String dataUrl = canvas1.toDataUrl("image/png");
-    assertTrue("toDataURL(image/png) should return data:image/png[data]", 
+    assertTrue("toDataURL(image/png) should return data:image/png[data]",
         dataUrl.startsWith("data:image/png"));
   }
 
@@ -139,7 +126,7 @@
     canvas1.setCoordinateSpaceHeight(140);
     canvas1.setCoordinateSpaceWidth(160);
     context.fillRect(2, 2, 300, 300);
-    
+
     assertEquals(41, canvas1.getOffsetHeight());
     assertEquals(61, canvas1.getOffsetWidth());
   }
@@ -167,7 +154,7 @@
     assertEquals(60, canvas1.getOffsetWidth());
     assertEquals(140, canvas1.getCoordinateSpaceHeight());
     assertEquals(160, canvas1.getCoordinateSpaceWidth());
-    
+
     // resize internal
     canvas1.setCoordinateSpaceHeight(141);
     canvas1.setCoordinateSpaceWidth(161);
@@ -184,5 +171,38 @@
       assertTrue(
           "isSupported() should be true when createIfSupported() returns non-null", Canvas.isSupported());
     }
+    // test the isxxxSupported() call if running known-sup or known-not-sup
+    // browsers
+    if (isFirefox35OrLater()) {
+      assertTrue(Canvas.isSupported());
+      assertTrue(Canvas.isSupported());
+    }
+    if (isIE6()) {
+      assertFalse(Canvas.isSupported());
+      assertFalse(Canvas.isSupported());
+    }
+  }
+
+  @Override
+  protected void gwtSetUp() throws Exception {
+    canvas1 = Canvas.createIfSupported();
+    canvas2 = Canvas.createIfSupported();
+
+    if (canvas1 == null) {
+      return; // don't continue if not supported
+    }
+
+    RootPanel.get().add(canvas1);
+    RootPanel.get().add(canvas2);
+  }
+
+  @Override
+  protected void gwtTearDown() throws Exception {
+    if (canvas1 == null) {
+      return; // don't continue if not supported
+    }
+
+    RootPanel.get().remove(canvas1);
+    RootPanel.get().remove(canvas2);
   }
 }
diff --git a/user/test/com/google/gwt/media/MediaSuite.java b/user/test/com/google/gwt/media/MediaSuite.java
index 1ecc7e4..de3542f 100644
--- a/user/test/com/google/gwt/media/MediaSuite.java
+++ b/user/test/com/google/gwt/media/MediaSuite.java
@@ -18,6 +18,8 @@
 
 import com.google.gwt.junit.tools.GWTTestSuite;
 import com.google.gwt.user.client.MediaEventsSinkTest;
+import com.google.gwt.media.client.AudioTest;
+import com.google.gwt.media.client.VideoTest;
 
 import junit.framework.Test;
 
@@ -29,13 +31,9 @@
     GWTTestSuite suite = new GWTTestSuite("Test suite for Media GWTTestCases");
     
     suite.addTestSuite(MediaEventsSinkTest.class);
+    suite.addTestSuite(AudioTest.class);
+    suite.addTestSuite(VideoTest.class);
 
-    /* 
-     * Tests disabled temporarily
-     * suite.addTestSuite(AudioTest.class);
-     * suite.addTestSuite(VideoTest.class);
-     */
-    
     return suite;
   }
 
diff --git a/user/test/com/google/gwt/media/client/AudioTest.java b/user/test/com/google/gwt/media/client/AudioTest.java
index 29b8b87..212c495 100644
--- a/user/test/com/google/gwt/media/client/AudioTest.java
+++ b/user/test/com/google/gwt/media/client/AudioTest.java
@@ -15,23 +15,22 @@
  */
 package com.google.gwt.media.client;
 
-import com.google.gwt.dom.client.AudioElement;
 import com.google.gwt.dom.client.MediaElement;
 import com.google.gwt.junit.DoNotRunWith;
 import com.google.gwt.junit.Platform;
 import com.google.gwt.user.client.ui.RootPanel;
 
 /**
- * Tests {@link AudioElement}.
+ * Tests {@link Audio}.
  *
- * Because HtmlUnit does not support HTML5, you will need to run these tests
+ *  Because HtmlUnit does not support HTML5, you will need to run these tests
  * manually in order to have them run. To do that, go to "run configurations" or
  * "debug configurations", select the test you would like to run, and put this
  * line in the VM args under the arguments tab: -Dgwt.args="-runStyle Manual:1"
  */
 @DoNotRunWith(Platform.HtmlUnitUnknown)
 public class AudioTest extends MediaTest {
-  Audio audio;
+  protected Audio audio;
 
   final static String audioUrlMp3 = "smallmp3.mp3";
   final static String audioFormatMp3 = "audio/mpeg";
@@ -39,46 +38,8 @@
   final static String audioFormatOgg = "audio/ogg";
 
   @Override
-  public MediaElement getElement() {
-    if (audio == null) {
-      return null;
-    }
-    return audio.getAudioElement();
-  }
-
-  @Override
-  public String getElementState() {
-    StringBuilder sb = new StringBuilder();
-    AudioElement e = audio.getAudioElement();
-    sb.append("AudioElement[");
-    sb.append("currentSrc=");
-    sb.append(e.getCurrentSrc());
-    sb.append(",currentTime=");
-    sb.append(e.getCurrentTime());
-    sb.append(",defaultPlaybackRate=");
-    sb.append(e.getDefaultPlaybackRate());
-    sb.append(",duration=");
-    sb.append(e.getDuration());
-    sb.append(",initialTime=");
-    sb.append(e.getInitialTime());
-    sb.append(",networkState=");
-    sb.append(e.getNetworkState());
-    sb.append(",playbackRate=");
-    sb.append(e.getPlaybackRate());
-    sb.append(",preload=");
-    sb.append(e.getPreload());
-    sb.append(",readyState=");
-    sb.append(e.getReadyState());
-    sb.append(",src=");
-    sb.append(e.getSrc());
-    sb.append(",startOffsetTime=");
-    sb.append(e.getStartOffsetTime());
-    sb.append(",seekable=");
-    sb.append(e.getSeekable());
-    sb.append(",volume=");
-    sb.append(e.getVolume());
-    sb.append("]");
-    return sb.toString();
+  public MediaBase getMedia() {
+    return audio;
   }
 
   @Override
@@ -89,21 +50,25 @@
   @Override
   protected void gwtSetUp() throws Exception {
     audio = Audio.createIfSupported();
-    
+
     if (audio == null) {
       return; // don't continue if not supported
     }
 
-    AudioElement element = audio.getAudioElement();
-    String canPlayMp3 = element.canPlayType(audioFormatMp3);
-    String canPlayOgg = element.canPlayType(audioFormatOgg);
-    if (!canPlayMp3.equalsIgnoreCase(MediaElement.CANNOT_PLAY)) {
-      element.setSrc(audioUrlMp3);
-    } else if (!canPlayOgg.equalsIgnoreCase(MediaElement.CANNOT_PLAY)) {
-      element.setSrc(audioUrlOgg);
+    String canPlayMp3 = audio.canPlayType(audioFormatMp3);
+    String canPlayOgg = audio.canPlayType(audioFormatOgg);
+    if (canPlayMp3.equals(MediaElement.CAN_PLAY_PROBABLY)) {
+      audio.setSrc(audioUrlMp3);
+    } else if (canPlayOgg.equals(MediaElement.CAN_PLAY_PROBABLY)) {
+      audio.setSrc(audioUrlOgg);
+    } else if (canPlayMp3.equals(MediaElement.CAN_PLAY_MAYBE)) {
+      audio.setSrc(audioUrlMp3);
+    } else if (canPlayOgg.equals(MediaElement.CAN_PLAY_MAYBE)) {
+      audio.setSrc(audioUrlOgg);
     } else {
       throw new Exception("Could not find suitable audio format");
     }
+
     RootPanel.get().add(audio);
   }
 
@@ -112,8 +77,11 @@
     if (audio == null) {
       return; // don't continue if not supported
     }
-    
-    audio.getAudioElement().pause();
+
+    // clean up
+    audio.pause();
+    audio.setSrc("");
+    audio.load();
     RootPanel.get().remove(audio);
   }
 }
diff --git a/user/test/com/google/gwt/media/client/MediaTest.java b/user/test/com/google/gwt/media/client/MediaTest.java
index 9eda220..d288211 100644
--- a/user/test/com/google/gwt/media/client/MediaTest.java
+++ b/user/test/com/google/gwt/media/client/MediaTest.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -19,17 +19,18 @@
 import com.google.gwt.junit.DoNotRunWith;
 import com.google.gwt.junit.Platform;
 import com.google.gwt.junit.client.GWTTestCase;
+import com.google.gwt.media.dom.client.MediaError;
 import com.google.gwt.user.client.Timer;
 
 import junit.framework.Assert;
 
 /**
- * Base test for {@link MediaElement}.
- * 
- * Do not call this class directly. To use, extend this class and override the
+ * Base test for {@link MediaBase}.
+ *
+ *  Do not call this class directly. To use, extend this class and override the
  * getElement and isSupported methods.
- * 
- * Because HtmlUnit does not support HTML5, you will need to run these tests
+ *
+ *  Because HtmlUnit does not support HTML5, you will need to run these tests
  * manually in order to have them run. To do that, go to "run configurations" or
  * "debug configurations", select the test you would like to run, and put this
  * line in the VM args under the arguments tab: -Dgwt.args="-runStyle Manual:1"
@@ -37,39 +38,50 @@
 @DoNotRunWith(Platform.HtmlUnitUnknown)
 public abstract class MediaTest extends GWTTestCase {
 
-  native boolean isOldFirefox() /*-{
-    return @com.google.gwt.dom.client.DOMImplMozilla::isGecko191OrBefore()();
+  static native boolean isFirefox35OrLater() /*-{
+    var geckoVersion = @com.google.gwt.dom.client.DOMImplMozilla::getGeckoVersion()();
+    return (geckoVersion != -1) && (geckoVersion >= 1009001);
   }-*/;
 
-  native boolean isFirefox40OrEarlier() /*-{
+  static native boolean isFirefox40OrEarlier() /*-{
     return @com.google.gwt.dom.client.DOMImplMozilla::isGecko2OrBefore()();
   }-*/;
 
-  public void disabled_testCurrentTime() {
-    MediaElement element = getElement();
-    if (element == null) {
+  static native boolean isIE6() /*-{
+    return @com.google.gwt.dom.client.DOMImplIE6::isIE6()();
+  }-*/;
+
+  static native boolean isOldFirefox() /*-{
+    return @com.google.gwt.dom.client.DOMImplMozilla::isGecko191OrBefore()();
+  }-*/;
+
+  public void disabled_testPreload() {
+    final MediaBase media = getMedia();
+    if (media == null) {
       return; // don't continue if not supported
     }
 
-    Assert.assertTrue("currentTime must be positive.",
-        element.getCurrentTime() >= 0.0);
+    if (isFirefox40OrEarlier()) {
+      return; // don't continue on older versions of Firefox.
+    }
 
-    double seekTime = 2.0; // seconds
-    element.setCurrentTime(seekTime);
-    Assert.assertEquals("currentTime must be able to be set.", seekTime,
-        element.getCurrentTime());
+    String state = media.getPreload();
+    assertNotNull(state);
+    assertTrue("Illegal preload state", state.equals(MediaElement.PRELOAD_AUTO)
+        || state.equals(MediaElement.PRELOAD_METADATA)
+        || state.equals(MediaElement.PRELOAD_NONE));
+
+    media.setPreload(MediaElement.PRELOAD_METADATA);
+    assertEquals("Preload should be able to be set.",
+        MediaElement.PRELOAD_METADATA, media.getPreload());
   }
 
   /**
-   * Return the MediaElement associated with the test.
-   * 
-   * @return the MediaElement associated with the test.
+   * Return the Media associated with the test.
+   *
+   * @return the Media associated with the test.
    */
-  public MediaElement getElement() {
-    return null;
-  }
-
-  public abstract String getElementState();
+  public abstract MediaBase getMedia();
 
   @Override
   public String getModuleName() {
@@ -77,70 +89,141 @@
   }
 
   public void testAutoPlay() {
-    MediaElement element = getElement();
-    if (element == null) {
+    final MediaBase media = getMedia();
+    if (media == null) {
       return; // don't continue if not supported
     }
 
-    element.setAutoplay(false);
-    assertFalse("Autoplay should be off.", element.isAutoplay());
-    element.setAutoplay(true);
-    assertTrue("Autoplay should be on.", element.isAutoplay());
+    media.setAutoplay(false);
+    assertFalse("Autoplay should be off.", media.isAutoplay());
+    media.setAutoplay(true);
+    assertTrue("Autoplay should be on.", media.isAutoplay());
   }
 
   public void testControls() {
-    MediaElement element = getElement();
-    if (element == null) {
+    final MediaBase media = getMedia();
+    if (media == null) {
       return; // don't continue if not supported
     }
 
-    element.setControls(false);
-    assertFalse("Controls should be off.", element.hasControls());
-    element.setControls(true);
-    assertTrue("Controls should be on.", element.hasControls());
+    media.setControls(false);
+    assertFalse("Controls should be off.", media.hasControls());
+    media.setControls(true);
+    assertTrue("Controls should be on.", media.hasControls());
   }
 
   public void testCurrentSrc() {
-    MediaElement element = getElement();
-    if (element == null) {
+    final MediaBase media = getMedia();
+    if (media == null) {
       return; // don't continue if not supported
     }
 
-    element.load();
-    Assert.assertNotNull("currentSrc should be set in these tests.",
-        element.getCurrentSrc());
+    media.load();
+    Assert.assertNotNull(
+        "currentSrc should be set in these tests.", media.getCurrentSrc());
+  }
+
+  public void testCurrentTime() {
+    final MediaBase media = getMedia();
+    if (media == null) {
+      return; // don't continue if not supported
+    }
+
+    delayTestFinish(25 * 1000);
+
+    // wait a little, then make sure it played and seek to a previous time
+    new Timer() {
+      @Override
+      public void run() {
+        MediaError error = media.getError();
+        if (error != null) {
+          fail("Media error (" + error.getCode() + ")");
+        }
+
+        // make sure it's playing
+        assertTrue("Media should have played", media.getCurrentTime() > 0);
+
+        // make sure it played enough
+        assertTrue(
+            "Did not play enough", 1000 * media.getCurrentTime() >= 6 * 1000);
+
+        // seek to a previous time
+        media.setCurrentTime(0.0);
+      }
+    }.schedule(15 * 1000);
+
+    // wait an additional 5000ms, then check that the seek was successful
+    new Timer() {
+      @Override
+      public void run() {
+        MediaError error = media.getError();
+        if (error != null) {
+          fail("Media error (" + error.getCode() + ")");
+        }
+
+        assertTrue(1000 * media.getCurrentTime() < 6 * 1000);
+        finishTest();
+      }
+    }.schedule(20 * 1000);
+
+    media.play();
+  }
+
+  public void testLoad() {
+    final MediaBase media = getMedia();
+    if (media == null) {
+      return; // don't continue if not supported
+    }
+
+    // the media resource needs time to load
+    delayTestFinish(20 * 1000);
+
+    // wait a little, then make sure it loaded
+    new Timer() {
+      @Override
+      public void run() {
+        MediaError error = media.getError();
+        if (error != null) {
+          fail("Media error (" + error.getCode() + ")");
+        }
+        finishTest();
+      }
+    }.schedule(15 * 1000);
+
+    media.load();
   }
 
   public void testLoop() {
-    MediaElement element = getElement();
-    if (element == null) {
+    final MediaBase media = getMedia();
+    if (media == null) {
       return; // don't continue if not supported
     }
 
-    element.setLoop(false);
-    assertFalse("Loop should be off.", element.isLoop());
-    element.setLoop(true);
-    assertTrue("Loop should be on.", element.isLoop());
+    media.setLoop(false);
+    assertFalse("Loop should be off.", media.isLoop());
+    media.setLoop(true);
+    assertTrue("Loop should be on.", media.isLoop());
   }
 
   public void testMuted() {
-    MediaElement element = getElement();
-    if (element == null) {
+    final MediaBase media = getMedia();
+    if (media == null) {
       return; // don't continue if not supported
     }
 
-    element.setMuted(true);
-    assertTrue("Muted should be true.", element.isMuted());
-    element.setMuted(false);
-    assertFalse("Muted should be false.", element.isMuted());
+    media.setMuted(true);
+    assertTrue("Muted should be true.", media.isMuted());
+    media.setMuted(false);
+    assertFalse("Muted should be false.", media.isMuted());
   }
 
   public void testNetworkState() {
-    MediaElement element = getElement();
-    if (element == null) {
+    final MediaBase media = getMedia();
+    if (media == null) {
       return; // don't continue if not supported
     }
-    int state = element.getNetworkState();
+
+    int state = media.getNetworkState();
     assertTrue("Illegal network state", state == MediaElement.NETWORK_EMPTY
         || state == MediaElement.NETWORK_IDLE
         || state == MediaElement.NETWORK_LOADING
@@ -148,88 +231,78 @@
   }
 
   public void testPlay() {
-    MediaElement element = getElement();
-    if (element == null) {
+    final MediaBase media = getMedia();
+    if (media == null) {
       return; // don't continue if not supported
     }
 
-    int waitMillis = 10000;
-    delayTestFinish(3 * waitMillis);
-
-    element.setPlaybackRate(1.0);
-    element.play();
+    // the media resource needs time to play
+    delayTestFinish(20 * 1000);
 
     // wait a little, then make sure it played
     new Timer() {
       @Override
       public void run() {
+        MediaError error = media.getError();
+        if (error != null) {
+          fail("Media error (" + error.getCode() + ")");
+        }
+
+        assertTrue(media.getCurrentTime() > 0);
         finishTest();
       }
-    }.schedule(waitMillis);
+    }.schedule(15 * 1000);
+
+    media.play();
   }
 
   public void testPlaybackRate() {
-    final MediaElement element = getElement();
-    if (element == null) {
+    final MediaBase media = getMedia();
+    if (media == null) {
       return; // don't continue if not supported
     }
 
-    int waitMillis = 5000;
-    delayTestFinish(3 * waitMillis);
-
     assertEquals("Default playback rate should be 1.0", 1.0,
-        element.getDefaultPlaybackRate());
+        media.getDefaultPlaybackRate());
 
-    element.play();
+    // the media resource needs time to play
+    delayTestFinish(20 * 1000);
 
-    // wait a little, then make sure it played
+    // wait a little, then change the playback rate
     new Timer() {
       @Override
       public void run() {
+        MediaError error = media.getError();
+        if (error != null) {
+          fail("Media error (" + error.getCode() + ")");
+        }
+
         // set rate to 2.0
         double rate = 2.0;
-        element.setPlaybackRate(rate);
+        media.setPlaybackRate(rate);
         assertEquals("Should be able to change playback rate", rate,
-            element.getPlaybackRate());
+            media.getPlaybackRate());
 
         // return to 1.0
         rate = 1.0;
-        element.setPlaybackRate(rate);
+        media.setPlaybackRate(rate);
         assertEquals("Should be able to change playback rate", rate,
-            element.getPlaybackRate());
+            media.getPlaybackRate());
 
         finishTest();
       }
-    }.schedule(waitMillis);
-  }
+    }.schedule(15 * 1000);
 
-  public void disabled_testPreload() {
-    MediaElement element = getElement();
-    if (element == null) {
-      return; // don't continue if not supported
-    }
-    if (isFirefox40OrEarlier()) {
-      return; // don't continue on older versions of Firefox.
-    }
-    
-    String state = element.getPreload();
-    assertNotNull(state);
-    assertTrue("Illegal preload state", state.equals(MediaElement.PRELOAD_AUTO)
-        || state.equals(MediaElement.PRELOAD_METADATA)
-        || state.equals(MediaElement.PRELOAD_NONE));
-
-    element.setPreload(MediaElement.PRELOAD_METADATA);
-    assertEquals("Preload should be able to be set.",
-        MediaElement.PRELOAD_METADATA, element.getPreload());
+    media.play();
   }
 
   public void testReadyState() {
-    MediaElement element = getElement();
-    if (element == null) {
+    final MediaBase media = getMedia();
+    if (media == null) {
       return; // don't continue if not supported
     }
 
-    int state = element.getReadyState();
+    int state = media.getReadyState();
     assertTrue("Illegal ready state", state == MediaElement.HAVE_CURRENT_DATA
         || state == MediaElement.HAVE_ENOUGH_DATA
         || state == MediaElement.HAVE_FUTURE_DATA
@@ -237,17 +310,29 @@
         || state == MediaElement.HAVE_NOTHING);
   }
 
+  public void testSupported() {
+    // test the isxxxSupported() call if running known sup or not sup browsers.
+    if (isIE6()) {
+      assertFalse(Audio.isSupported());
+      assertFalse(Video.isSupported());
+    }
+    if (isFirefox35OrLater()) {
+      assertTrue(Audio.isSupported());
+      assertTrue(Video.isSupported());
+    }
+  }
+
   public void testVolume() {
-    MediaElement element = getElement();
-    if (element == null) {
+    final MediaBase media = getMedia();
+    if (media == null) {
       return; // don't continue if not supported
     }
 
-    element.setVolume(0.5);
-    assertEquals("Volume should be at one-half loudness.", 0.5,
-        element.getVolume());
-    element.setVolume(0.75);
+    media.setVolume(0.5);
+    assertEquals(
+        "Volume should be at one-half loudness.", 0.5, media.getVolume());
+    media.setVolume(0.75);
     assertEquals("Volume should be at three-quarters loudness.", 0.75,
-        element.getVolume());
+        media.getVolume());
   }
 }
diff --git a/user/test/com/google/gwt/media/client/VideoTest.java b/user/test/com/google/gwt/media/client/VideoTest.java
index 05cecfe..115c107 100644
--- a/user/test/com/google/gwt/media/client/VideoTest.java
+++ b/user/test/com/google/gwt/media/client/VideoTest.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -16,23 +16,23 @@
 package com.google.gwt.media.client;
 
 import com.google.gwt.dom.client.MediaElement;
-import com.google.gwt.dom.client.VideoElement;
 import com.google.gwt.junit.DoNotRunWith;
 import com.google.gwt.junit.Platform;
+import com.google.gwt.media.dom.client.MediaError;
 import com.google.gwt.user.client.Timer;
 import com.google.gwt.user.client.ui.RootPanel;
 
 /**
- * Tests {@link VideoElement}.
- * 
- * Because HtmlUnit does not support HTML5, you will need to run these tests
+ * Tests {@link Video}.
+ *
+ *  Because HtmlUnit does not support HTML5, you will need to run these tests
  * manually in order to have them run. To do that, go to "run configurations" or
  * "debug configurations", select the test you would like to run, and put this
  * line in the VM args under the arguments tab: -Dgwt.args="-runStyle Manual:1"
  */
 @DoNotRunWith(Platform.HtmlUnitUnknown)
 public class VideoTest extends MediaTest {
-  Video video;
+  protected Video video;
 
   final static String posterUrl = "poster.jpg";
   final static String videoUrlH264 = "smallh264.mp4";
@@ -40,61 +40,12 @@
   final static String videoUrlOgv = "smalltheora.ogv";
   final static String videoFormatOgv = "video/ogg; codecs=\"theora, vorbis\"";
 
-  final static int videoWidth = 32;
-  final static int videoHeight = 18;
+  final static int videoWidth = 64;
+  final static int videoHeight = 36;
 
   @Override
-  public MediaElement getElement() {
-    if (video == null) {
-      return null;
-    }
-    return video.getVideoElement();
-  }
-
-  @Override
-  public String getElementState() {
-    StringBuilder sb = new StringBuilder();
-    VideoElement e = video.getVideoElement();
-
-    sb.append("VideoElement[");
-    sb.append("currentSrc=");
-    sb.append(e.getCurrentSrc());
-    sb.append(",currentTime=");
-    sb.append(e.getCurrentTime());
-    sb.append(",defaultPlaybackRate=");
-    sb.append(e.getDefaultPlaybackRate());
-    sb.append(",duration=");
-    sb.append(e.getDuration());
-    sb.append(",height=");
-    sb.append(e.getHeight());
-    sb.append(",initialTime=");
-    sb.append(e.getInitialTime());
-    sb.append(",networkState=");
-    sb.append(e.getNetworkState());
-    sb.append(",playbackRate=");
-    sb.append(e.getPlaybackRate());
-    sb.append(",poster=");
-    sb.append(e.getPoster());
-    sb.append(",preload=");
-    sb.append(e.getPreload());
-    sb.append(",readyState=");
-    sb.append(e.getReadyState());
-    sb.append(",src=");
-    sb.append(e.getSrc());
-    sb.append(",startOffsetTime=");
-    sb.append(e.getStartOffsetTime());
-    sb.append(",seekable=");
-    sb.append(e.getSeekable());
-    sb.append(",videoHeight=");
-    sb.append(e.getVideoHeight());
-    sb.append(",videoWidth=");
-    sb.append(e.getVideoWidth());
-    sb.append(",volume=");
-    sb.append(e.getVolume());
-    sb.append(",width=");
-    sb.append(e.getWidth());
-    sb.append("]");
-    return sb.toString();
+  public MediaBase getMedia() {
+    return video;
   }
 
   @Override
@@ -107,9 +58,8 @@
       return; // don't continue if not supported
     }
 
-    VideoElement element = video.getVideoElement();
-    element.setPoster(posterUrl);
-    String poster = element.getPoster();
+    video.setPoster(posterUrl);
+    String poster = video.getPoster();
     assertEquals(posterUrl, poster.substring(poster.lastIndexOf('/') + 1));
   }
 
@@ -120,11 +70,24 @@
 
     int width = 100;
     int height = 200;
-    VideoElement element = video.getVideoElement();
-    element.setWidth(width);
-    element.setHeight(height);
-    assertEquals(width, element.getWidth());
-    assertEquals(height, element.getHeight());
+    video.setWidth(width + "px");
+    video.setHeight(height + "px");
+    assertEquals(width, video.getOffsetWidth());
+    assertEquals(height, video.getOffsetHeight());
+  }
+
+  // test that the deprecated src constructor works
+  public void testSrcConstructor() {
+    if (video == null) {
+      return; // don't continue if not supported
+    }
+
+    Video video = new Video("http://google.com/video");
+    assertNotNull(video);
+    assertEquals("http://google.com/video", video.getSrc());
+    video.setSrc("");
+    video.load();
+    RootPanel.get().remove(video);
   }
 
   public void testVideoSize() {
@@ -132,43 +95,48 @@
       return; // don't continue if not supported
     }
 
-    int waitMillis = 5000;
-    delayTestFinish(3 * waitMillis);
+    // the media resource needs time to load
+    delayTestFinish(20 * 1000);
 
-    final VideoElement element = video.getVideoElement();
-    element.play();
-
-    // wait a little, then make sure it played
+    // wait a little, then make sure it loaded
     new Timer() {
       @Override
       public void run() {
-        assertEquals("Element = " + getElementState() + ", expected width "
-            + videoWidth, videoWidth, element.getVideoWidth());
-        assertEquals("Element = " + getElementState() + ", expected height "
-            + videoHeight, videoHeight, element.getVideoHeight());
+        MediaError error = video.getError();
+        if (error != null) {
+          fail("Media error (" + error.getCode() + ")");
+        }
+        assertEquals(videoWidth, video.getVideoWidth());
+        assertEquals(videoHeight, video.getVideoHeight());
         finishTest();
       }
-    }.schedule(waitMillis);
+    }.schedule(15 * 1000);
+
+    video.play();
   }
 
   @Override
   protected void gwtSetUp() throws Exception {
     video = Video.createIfSupported();
-    
+
     if (video == null) {
       return; // don't continue if not supported
     }
 
-    VideoElement element = video.getVideoElement();
-    if (!element.canPlayType(videoFormatH264).equalsIgnoreCase(
-        MediaElement.CANNOT_PLAY)) {
-      element.setSrc(videoUrlH264);
-    } else if (!element.canPlayType(videoFormatOgv).equalsIgnoreCase(
-        MediaElement.CANNOT_PLAY)) {
-      element.setSrc(videoUrlOgv);
+    String canPlayH264 = video.canPlayType(videoFormatH264);
+    String canPlayOgv = video.canPlayType(videoFormatOgv);
+    if (canPlayH264.equals(MediaElement.CAN_PLAY_PROBABLY)) {
+      video.setSrc(videoUrlH264);
+    } else if (canPlayOgv.equals(MediaElement.CAN_PLAY_PROBABLY)) {
+      video.setSrc(videoUrlOgv);
+    } else if (canPlayH264.equals(MediaElement.CAN_PLAY_MAYBE)) {
+      video.setSrc(videoUrlH264);
+    } else if (canPlayOgv.equals(MediaElement.CAN_PLAY_MAYBE)) {
+      video.setSrc(videoUrlOgv);
     } else {
       throw new Exception("Could not find suitable video format");
     }
+
     RootPanel.get().add(video);
   }
 
@@ -177,8 +145,11 @@
     if (video == null) {
       return; // don't continue if not supported
     }
-    
-    video.getVideoElement().pause();
+
+    // clean up
+    video.pause();
+    video.setSrc("");
+    video.load();
     RootPanel.get().remove(video);
   }
 }
diff --git a/user/test/com/google/gwt/media/public-test/poster.jpg b/user/test/com/google/gwt/media/public-test/poster.jpg
index 980ed41..3ad24d8 100644
--- a/user/test/com/google/gwt/media/public-test/poster.jpg
+++ b/user/test/com/google/gwt/media/public-test/poster.jpg
Binary files differ
diff --git a/user/test/com/google/gwt/media/public-test/smallh264.mp4 b/user/test/com/google/gwt/media/public-test/smallh264.mp4
index 1ec0fea..b9b1bff 100644
--- a/user/test/com/google/gwt/media/public-test/smallh264.mp4
+++ b/user/test/com/google/gwt/media/public-test/smallh264.mp4
Binary files differ
diff --git a/user/test/com/google/gwt/media/public-test/smalltheora.ogv b/user/test/com/google/gwt/media/public-test/smalltheora.ogv
index c0950be..48c3df9 100644
--- a/user/test/com/google/gwt/media/public-test/smalltheora.ogv
+++ b/user/test/com/google/gwt/media/public-test/smalltheora.ogv
Binary files differ