Bug 449159 - Implement seeking in the Ogg backend - r+sr=roc
☠☠ backed out by 24a7034e1880 ☠ ☠
authorChris Double <chris.double@double.co.nz>
Thu, 25 Sep 2008 16:25:15 +1200
changeset 19675 c7c42974317d4e0d0ed16d616e44cfc335824328
parent 19674 c1d414f7adf306385c8537cc6f32349b0e0b778d
child 19676 62295a6ef00a0282efd16c7a6e15c498125b5fe7
child 19690 24a7034e18801326ce0b85f97d8880654780db81
push idunknown
push userunknown
push dateunknown
bugs449159
milestone1.9.1b1pre
Bug 449159 - Implement seeking in the Ogg backend - r+sr=roc
content/html/content/public/nsHTMLAudioElement.h
content/html/content/public/nsHTMLMediaElement.h
content/html/content/public/nsHTMLVideoElement.h
content/html/content/src/nsHTMLMediaElement.cpp
content/html/content/src/nsHTMLVideoElement.cpp
content/media/video/public/Makefile.in
content/media/video/public/nsChannelReader.h
content/media/video/public/nsChannelToPipeListener.h
content/media/video/public/nsMediaDecoder.h
content/media/video/public/nsMediaStream.h
content/media/video/public/nsOggDecoder.h
content/media/video/public/nsVideoDecoder.h
content/media/video/src/Makefile.in
content/media/video/src/nsChannelReader.cpp
content/media/video/src/nsChannelToPipeListener.cpp
content/media/video/src/nsMediaDecoder.cpp
content/media/video/src/nsMediaStream.cpp
content/media/video/src/nsOggDecoder.cpp
content/media/video/src/nsVideoDecoder.cpp
layout/build/nsLayoutStatics.cpp
--- a/content/html/content/public/nsHTMLAudioElement.h
+++ b/content/html/content/public/nsHTMLAudioElement.h
@@ -32,17 +32,16 @@
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 #include "nsIDOMHTMLAudioElement.h"
 #include "nsHTMLMediaElement.h"
-#include "nsVideoDecoder.h"
 
 typedef PRUint16 nsMediaNetworkState;
 typedef PRUint16 nsMediaReadyState;
 
 class nsHTMLAudioElement : public nsHTMLMediaElement,
                            public nsIDOMHTMLAudioElement
 {
 public:
--- a/content/html/content/public/nsHTMLMediaElement.h
+++ b/content/html/content/public/nsHTMLMediaElement.h
@@ -32,17 +32,17 @@
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 #include "nsIDOMHTMLMediaElement.h"
 #include "nsGenericHTMLElement.h"
-#include "nsVideoDecoder.h"
+#include "nsMediaDecoder.h"
 
 // Define to output information on decoding and painting framerate
 /* #define DEBUG_FRAME_RATE 1 */
 
 typedef PRUint16 nsMediaNetworkState;
 typedef PRUint16 nsMediaReadyState;
 
 class nsHTMLMediaElement : public nsGenericHTMLElement
@@ -92,54 +92,81 @@ public:
   void ResourceLoaded();
 
   // Called by the video decoder object, on the main thread,
   // when the resource has a network error during loading.
   void NetworkError();
 
   // Called by the video decoder object, on the main thread,
   // when the video playback has ended.
-  void PlaybackCompleted();
+  void PlaybackEnded();
 
   // Called by the decoder object, on the main thread, when
   // approximately enough of the resource has been loaded to play
   // through without pausing for buffering.
   void CanPlayThrough();
 
-  // Draw the latest video data. See nsVideoDecoder for 
+  // Called by the video decoder object, on the main thread,
+  // when the resource has started seeking.
+  void SeekStarted();
+
+  // Called by the video decoder object, on the main thread,
+  // when the resource has completed seeking.
+  void SeekCompleted();
+
+  // Draw the latest video data. See nsMediaDecoder for 
   // details.
   void Paint(gfxContext* aContext, const gfxRect& aRect);
 
   // Dispatch events
   nsresult DispatchSimpleEvent(const nsAString& aName);
   nsresult DispatchProgressEvent(const nsAString& aName);
   nsresult DispatchAsyncSimpleEvent(const nsAString& aName);
   nsresult DispatchAsyncProgressEvent(const nsAString& aName);
 
   // Use this method to change the mReadyState member, so required
   // events can be fired.
   void ChangeReadyState(nsMediaReadyState aState);
 
+  // Is the media element actively playing as defined by the HTML 5 specification.
+  // http://www.whatwg.org/specs/web-apps/current-work/#actively
+  PRBool IsActivelyPlaying() const;
+
+  // Has playback ended as defined by the HTML 5 specification.
+  // http://www.whatwg.org/specs/web-apps/current-work/#ended
+  PRBool IsPlaybackEnded() const;
+
   // principal of the currently playing stream
   nsIPrincipal* GetCurrentPrincipal();
 
+  // Update the visual size of the media. Called from the decoder on the
+  // main thread when/if the size changes.
+  void UpdateMediaSize(nsIntSize size);
+
 protected:
   nsresult PickMediaElement(nsAString& aChosenMediaResource);
   virtual nsresult InitializeDecoder(nsAString& aChosenMediaResource);
 
-  nsRefPtr<nsVideoDecoder> mDecoder;
+  nsRefPtr<nsMediaDecoder> mDecoder;
 
   // Error attribute
   nsCOMPtr<nsIDOMHTMLMediaError> mError;
 
   // Media loading flags. See: 
   //   http://www.whatwg.org/specs/web-apps/current-work/#video)
   nsMediaNetworkState mNetworkState;
   nsMediaReadyState mReadyState;
 
+  // Value of the volume before it was muted
+  float mMutedVolume; 
+
+  // Size of the media. Updated by the decoder on the main thread if
+  // it changes. Defaults to a width and height of -1 if not set.
+  nsIntSize mMediaSize;
+
   // If true then we have begun downloading the media content.
   // Set to false when completed, or not yet started.
   PRPackedBool mBegun;
 
   // If truen then the video playback has completed.
   PRPackedBool mEnded;
 
   // True when the decoder has loaded enough data to display the
@@ -156,21 +183,21 @@ protected:
   // 'mAutoplaying' flag, which indicates whether the current playback
   // is a result of the autoplay attribute.
   PRPackedBool mAutoplaying;
 
   // Playback of the video is paused either due to calling the
   // 'Pause' method, or playback not yet having started.
   PRPackedBool mPaused;
 
-  // True if we are currently seeking through the media file.
-  PRPackedBool mSeeking;
-
   // True if the sound is muted
   PRPackedBool mMuted;
 
-  // Value of the volume before it was muted
-  float mMutedVolume; 
-
   // Flag to indicate if the child elements (eg. <source/>) have been
   // parsed.
   PRPackedBool mIsDoneAddingChildren;
+
+  // If TRUE then the media element was actively playing before the currently
+  // in progress seeking. If FALSE then the media element is either not seeking
+  // or was not actively playing before the current seek. Used to decide whether
+  // to raise the 'waiting' event as per 4.7.1.8 in HTML 5 specification.
+  PRPackedBool mPlayingBeforeSeek;
 };
--- a/content/html/content/public/nsHTMLVideoElement.h
+++ b/content/html/content/public/nsHTMLVideoElement.h
@@ -32,17 +32,16 @@
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 #include "nsIDOMHTMLVideoElement.h"
 #include "nsHTMLMediaElement.h"
-#include "nsVideoDecoder.h"
 
 class nsHTMLVideoElement : public nsHTMLMediaElement,
                            public nsIDOMHTMLVideoElement
 {
 public:
   nsHTMLVideoElement(nsINodeInfo *aNodeInfo, PRBool aFromParser = PR_FALSE);
   virtual ~nsHTMLVideoElement();
 
@@ -69,14 +68,13 @@ public:
                               nsIContent* aBindingParent,
                               PRBool aCompileEventHandlers);
   virtual void UnbindFromTree(PRBool aDeep = PR_TRUE,
                               PRBool aNullParent = PR_TRUE);
 
   // Returns the current video frame width and height.
   // If there is no video frame, returns the given default size.
   nsIntSize GetVideoSize(nsIntSize defaultSize);
-  double GetVideoFramerate();
 
 protected:
   virtual nsresult InitializeDecoder(nsAString& aChosenMediaResource);
 
 };
--- a/content/html/content/src/nsHTMLMediaElement.cpp
+++ b/content/html/content/src/nsHTMLMediaElement.cpp
@@ -194,17 +194,16 @@ NS_IMETHODIMP nsHTMLMediaElement::Load()
   float rate = 1.0;
   GetDefaultPlaybackRate(&rate);
   SetPlaybackRate(rate);
 
   if (mNetworkState != nsIDOMHTMLMediaElement::EMPTY) {
     mNetworkState = nsIDOMHTMLMediaElement::EMPTY;
     ChangeReadyState(nsIDOMHTMLMediaElement::DATA_UNAVAILABLE);
     mPaused = PR_TRUE;
-    mSeeking = PR_FALSE;
     // TODO: The current playback position must be set to 0.
     // TODO: The currentLoop DOM attribute must be set to 0.
     DispatchSimpleEvent(NS_LITERAL_STRING("emptied"));
   }
 
   nsAutoString chosenMediaResource;
   nsresult rv = PickMediaElement(chosenMediaResource);
   NS_ENSURE_SUCCESS(rv, rv);
@@ -229,31 +228,40 @@ NS_IMETHODIMP nsHTMLMediaElement::GetRea
   *aReadyState = mReadyState;
 
   return NS_OK;
 }
 
 /* readonly attribute boolean seeking; */
 NS_IMETHODIMP nsHTMLMediaElement::GetSeeking(PRBool *aSeeking)
 {
-  *aSeeking = mSeeking;
+  *aSeeking = mDecoder && mDecoder->IsSeeking();
 
   return NS_OK;
 }
 
 /* attribute float currentTime; */
 NS_IMETHODIMP nsHTMLMediaElement::GetCurrentTime(float *aCurrentTime)
 {
   *aCurrentTime = mDecoder ? mDecoder->GetCurrentTime() : 0.0;
   return NS_OK;
 }
 
 NS_IMETHODIMP nsHTMLMediaElement::SetCurrentTime(float aCurrentTime)
 {
-  return mDecoder ? mDecoder->Seek(aCurrentTime) : NS_ERROR_DOM_INVALID_STATE_ERR;
+  if (!mDecoder)
+    return NS_ERROR_DOM_INVALID_STATE_ERR;
+
+  if (mNetworkState < nsIDOMHTMLMediaElement::LOADED_METADATA) 
+    return NS_ERROR_DOM_INVALID_STATE_ERR;
+
+  mPlayingBeforeSeek = IsActivelyPlaying();
+  nsresult rv = mDecoder->Seek(aCurrentTime);
+  DispatchAsyncSimpleEvent(NS_LITERAL_STRING("timeupdate"));
+  return rv;
 }
 
 /* readonly attribute float duration; */
 NS_IMETHODIMP nsHTMLMediaElement::GetDuration(float *aDuration)
 {
   *aDuration =  mDecoder ? mDecoder->GetDuration() : 0.0;
   return NS_OK;
 }
@@ -389,25 +397,26 @@ NS_IMETHODIMP nsHTMLMediaElement::SetMut
     DispatchSimpleEvent(NS_LITERAL_STRING("volumechange"));
   return NS_OK;
 }
 
 nsHTMLMediaElement::nsHTMLMediaElement(nsINodeInfo *aNodeInfo, PRBool aFromParser)
   : nsGenericHTMLElement(aNodeInfo),
     mNetworkState(nsIDOMHTMLMediaElement::EMPTY),
     mReadyState(nsIDOMHTMLMediaElement::DATA_UNAVAILABLE),
+    mMutedVolume(0.0),
+    mMediaSize(-1,-1),
     mBegun(PR_FALSE),
     mEnded(PR_FALSE),
     mLoadedFirstFrame(PR_FALSE),
     mAutoplaying(PR_TRUE),
     mPaused(PR_TRUE),
-    mSeeking(PR_FALSE),
     mMuted(PR_FALSE),
-    mMutedVolume(0.0),
-    mIsDoneAddingChildren(!aFromParser)
+    mIsDoneAddingChildren(!aFromParser),
+    mPlayingBeforeSeek(PR_FALSE)
 {
 }
 
 nsHTMLMediaElement::~nsHTMLMediaElement()
 {
   if (mDecoder) 
     mDecoder->Stop();
 }
@@ -467,17 +476,17 @@ nsHTMLMediaElement::ParseAttribute(PRInt
     else if (ParseImageAttribute(aAttribute, aValue, aResult)) {
       return PR_TRUE;
     }
   }
 
   return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
                                               aResult);
 }
-#include "nsString.h"
+
 nsresult
 nsHTMLMediaElement::SetAttr(PRInt32 aNameSpaceID, nsIAtom* aName,
                             nsIAtom* aPrefix, const nsAString& aValue,
                             PRBool aNotify)
 {
   nsresult rv = 
     nsGenericHTMLElement::SetAttr(aNameSpaceID, aName, aPrefix, aValue,
                                     aNotify);
@@ -644,32 +653,46 @@ void nsHTMLMediaElement::NetworkError()
 {
   mError = new nsHTMLMediaError(nsHTMLMediaError::MEDIA_ERR_NETWORK);
   mBegun = PR_FALSE;
   DispatchProgressEvent(NS_LITERAL_STRING("error"));
   mNetworkState = nsIDOMHTMLMediaElement::EMPTY;
   DispatchSimpleEvent(NS_LITERAL_STRING("empty"));
 }
 
-void nsHTMLMediaElement::PlaybackCompleted()
+void nsHTMLMediaElement::PlaybackEnded()
 {
   mBegun = PR_FALSE;
   mEnded = PR_TRUE;
-  Pause();
   SetCurrentTime(0);
   DispatchSimpleEvent(NS_LITERAL_STRING("ended"));
 }
 
 void nsHTMLMediaElement::CanPlayThrough()
 {
   ChangeReadyState(nsIDOMHTMLMediaElement::CAN_PLAY_THROUGH);
 }
 
+void nsHTMLMediaElement::SeekStarted()
+{
+  DispatchAsyncSimpleEvent(NS_LITERAL_STRING("seeking"));
+}
+
+void nsHTMLMediaElement::SeekCompleted()
+{
+  mPlayingBeforeSeek = PR_FALSE;
+  DispatchAsyncSimpleEvent(NS_LITERAL_STRING("seeked"));
+}
+
 void nsHTMLMediaElement::ChangeReadyState(nsMediaReadyState aState)
 {
+  // Handle raising of "waiting" event during seek (see 4.7.10.8)
+  if (mPlayingBeforeSeek && aState <= nsIDOMHTMLMediaElement::CAN_PLAY)
+    DispatchAsyncSimpleEvent(NS_LITERAL_STRING("waiting"));
+    
   mReadyState = aState;
   if (mNetworkState != nsIDOMHTMLMediaElement::EMPTY) {
     switch(mReadyState) {
     case nsIDOMHTMLMediaElement::DATA_UNAVAILABLE:
       DispatchAsyncSimpleEvent(NS_LITERAL_STRING("dataunavailable"));
       LOG(PR_LOG_DEBUG, ("Ready state changed to DATA_UNAVAILABLE"));
       break;
       
@@ -715,26 +738,24 @@ nsresult nsHTMLMediaElement::DispatchSim
                                               aName, 
                                               PR_TRUE, 
                                               PR_TRUE);
 }
 
 nsresult nsHTMLMediaElement::DispatchAsyncSimpleEvent(const nsAString& aName)
 {
   nsCOMPtr<nsIRunnable> event = new nsAsyncEventRunner(aName, this, PR_FALSE);
-  if (event)
-    NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL); 
+  NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL); 
   return NS_OK;                           
 }
 
 nsresult nsHTMLMediaElement::DispatchAsyncProgressEvent(const nsAString& aName)
 {
   nsCOMPtr<nsIRunnable> event = new nsAsyncEventRunner(aName, this, PR_TRUE);
-  if (event)
-    NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL); 
+  NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL); 
   return NS_OK;                           
 }
 
 nsresult nsHTMLMediaElement::DispatchProgressEvent(const nsAString& aName)
 {
   if (!mDecoder)
     return NS_OK;
 
@@ -769,16 +790,41 @@ nsresult nsHTMLMediaElement::DoneAddingC
   return NS_OK;
 }
 
 PRBool nsHTMLMediaElement::IsDoneAddingChildren()
 {
   return mIsDoneAddingChildren;
 }
 
+PRBool nsHTMLMediaElement::IsActivelyPlaying() const
+{
+  // TODO: 
+  //   playback has not stopped due to errors, 
+  //   and the element has not paused for user interaction
+  return 
+    !mPaused && 
+    (mReadyState == nsIDOMHTMLMediaElement::CAN_PLAY || 
+     mReadyState == nsIDOMHTMLMediaElement::CAN_PLAY_THROUGH) &&
+    !IsPlaybackEnded();
+}
+PRBool nsHTMLMediaElement::IsPlaybackEnded() const
+{
+  // TODO:
+  //   the current playback position is equal to the effective end of the media resource, 
+  //   and the currentLoop attribute is equal to playCount-1. 
+  //   See bug 449157.
+  return mNetworkState >= nsIDOMHTMLMediaElement::LOADED_METADATA && mEnded;
+}
+
 nsIPrincipal*
 nsHTMLMediaElement::GetCurrentPrincipal()
 {
   if (!mDecoder)
     return nsnull;
 
   return mDecoder->GetCurrentPrincipal();
 }
+
+void nsHTMLMediaElement::UpdateMediaSize(nsIntSize size)
+{
+  mMediaSize = size;
+}
--- a/content/html/content/src/nsHTMLVideoElement.cpp
+++ b/content/html/content/src/nsHTMLVideoElement.cpp
@@ -83,55 +83,41 @@ NS_IMPL_ELEMENT_CLONE(nsHTMLVideoElement
 NS_IMPL_INT_ATTR(nsHTMLVideoElement, Width, width)
 NS_IMPL_INT_ATTR(nsHTMLVideoElement, Height, height)
 NS_IMPL_URI_ATTR(nsHTMLVideoElement, Poster, poster)
 
 // nsIDOMHTMLVideoElement
 /* readonly attribute unsigned long videoWidth; */
 NS_IMETHODIMP nsHTMLVideoElement::GetVideoWidth(PRUint32 *aVideoWidth)
 {
-  nsIntSize size(0,0);
-  if (mDecoder) 
-    size = mDecoder->GetVideoSize(size);
-
-  *aVideoWidth = size.width;
+  *aVideoWidth = mMediaSize.width == -1 ? 0 : mMediaSize.width;
   return NS_OK;
 }
 
 /* readonly attribute unsigned long videoHeight; */
 NS_IMETHODIMP nsHTMLVideoElement::GetVideoHeight(PRUint32 *aVideoHeight)
 {
-  nsIntSize size(0,0);
-  if (mDecoder) 
-    size = mDecoder->GetVideoSize(size);
-
-  *aVideoHeight = size.height;
+  *aVideoHeight = mMediaSize.height == -1 ? 0 : mMediaSize.height;
   return NS_OK;
 }
 
-
 nsHTMLVideoElement::nsHTMLVideoElement(nsINodeInfo *aNodeInfo, PRBool aFromParser)
   : nsHTMLMediaElement(aNodeInfo, aFromParser)
 {
 }
 
 nsHTMLVideoElement::~nsHTMLVideoElement()
 {
 }
 
-nsIntSize nsHTMLVideoElement::GetVideoSize(nsIntSize defaultSize)
+nsIntSize nsHTMLVideoElement::GetVideoSize(nsIntSize aDefaultSize)
 {
-  return mDecoder ? mDecoder->GetVideoSize(defaultSize) : defaultSize;
+  return mMediaSize.width == -1 && mMediaSize.height == -1 ? aDefaultSize : mMediaSize;
 }
 
-double nsHTMLVideoElement::GetVideoFramerate() {
-  return mDecoder ? mDecoder->GetVideoFramerate() : 0.0;
-}
-
-
 nsresult nsHTMLVideoElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
                                         nsIContent* aBindingParent,
                                         PRBool aCompileEventHandlers)
 {
   if (mDecoder)
     mDecoder->ElementAvailable(this);
 
   return nsHTMLMediaElement::BindToTree(aDocument, 
--- a/content/media/video/public/Makefile.in
+++ b/content/media/video/public/Makefile.in
@@ -39,20 +39,22 @@ topsrcdir	= @top_srcdir@
 srcdir		= @srcdir@
 VPATH		= @srcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
 MODULE		= content
 
 EXPORTS		= \
-		nsVideoDecoder.h \
+		nsMediaDecoder.h \
 		$(NULL)
 
 ifdef MOZ_OGG
 EXPORTS		+= \
 		nsAudioStream.h \
 		nsChannelReader.h \
+		nsChannelToPipeListener.h \
+		nsMediaStream.h \
 		nsOggDecoder.h \
 		$(NULL)
 endif
 
 include $(topsrcdir)/config/rules.mk
--- a/content/media/video/public/nsChannelReader.h
+++ b/content/media/video/public/nsChannelReader.h
@@ -1,9 +1,8 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* ***** BEGIN LICENSE BLOCK *****
  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  *
  * The contents of this file are subject to the Mozilla Public License Version
  * 1.1 (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.mozilla.org/MPL/
@@ -30,93 +29,59 @@
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
-#if !defined(nsChannelReader_h___)
-#define nsChannelReader_h___
+#if !defined(nsChannelReader_h_)
+#define nsChannelReader_h_
 
-#include "nsCOMPtr.h"
-#include "nsIChannel.h"
-#include "nsIInputStream.h"
-#include "nsIOutputStream.h"
-#include "nsIRequestObserver.h"
-#include "nsIStreamListener.h"
+#include "nsAutoPtr.h"
+#include "nsMediaStream.h"
+#include "nsMediaDecoder.h"
 #include "nsIPrincipal.h"
 
 #include "oggplay/oggplay.h"
 
-class nsOggDecoder;
-
-/* 
-   Reads all data on the input stream of a channel and
-   writes it to a pipe. This allows a seperate thread to
-   read data from a channel running on the main thread
-*/
-class nsChannelToPipeListener : public nsIStreamListener
-{
-  // ISupports
-  NS_DECL_ISUPPORTS
-
-  // IRequestObserver
-  NS_DECL_NSIREQUESTOBSERVER
-
-  // IStreamListener
-  NS_DECL_NSISTREAMLISTENER
-
-  public:
-  nsChannelToPipeListener(nsOggDecoder* aDecoder);
-  nsresult Init();
-  void GetInputStream(nsIInputStream** aStream);
-  void Stop();
-  double BytesPerSecond() const;
-
-  nsIPrincipal* GetCurrentPrincipal();
-
-private:
-  nsCOMPtr<nsIInputStream> mInput;
-  nsCOMPtr<nsIOutputStream> mOutput;
-  nsCOMPtr<nsIPrincipal> mPrincipal;
-  nsOggDecoder* mDecoder;
-
-  // Interval when download started. Used in
-  // computing bytes per second download rate.
-  PRIntervalTime mIntervalStart;
-
-  // Interval when last downloaded bytes occurred. Used in computer
-  // bytes per second download rate.
-  PRIntervalTime mIntervalEnd;
-
-  // Total bytes transferred so far
-  PRInt64 mTotalBytes;
-};
-
 class nsChannelReader : public OggPlayReader
 {
 public:
   nsChannelReader();
-  nsresult Init(nsOggDecoder* aDecoder, nsIURI* aURI);
+  ~nsChannelReader();
+
+  // Initialize the reader with the given decoder and URI.
+  nsresult Init(nsMediaDecoder* aDecoder, nsIURI* aURI);
+
+  // Cancel any blocking request currently in progress and cause that
+  // request to return an error. Call on main thread only.
+  void Cancel();
+
+  // Return the number of bytes buffered from the file. This can safely
+  // be read without blocking.
   PRUint32 Available();
+
+  // Return average number of bytes per second that the 
+  // download of the media resource is achieving.
+  float DownloadRate();
+
+  // Return average number of bytes per second that the 
+  // playback of the media resource is achieving.
+  float PlaybackRate();
+
+  nsIPrincipal* GetCurrentPrincipal();
+  
+  // Implementation of OggPlay Reader API.
   OggPlayErrorCode initialise(int aBlock);
   OggPlayErrorCode destroy();
   size_t io_read(char* aBuffer, size_t aCount);
   int io_seek(long aOffset, int aWhence);
   long io_tell();  
-
-  // Return average number of bytes per second that the 
-  // download of the media resource is achieving.
-  double BytesPerSecond() const;
-
-  // return the principal that we saved in GetRequest
-  nsIPrincipal* GetCurrentPrincipal();
-
+  int duration();
+  
 public:
-  nsCOMPtr<nsIChannel>  mChannel;
-  nsCOMPtr<nsIInputStream>  mInput;
-  nsCOMPtr<nsChannelToPipeListener> mListener;
+  nsMediaStream mStream;
   unsigned long mCurrentPosition;
 };
 
 #endif
new file mode 100644
--- /dev/null
+++ b/content/media/video/public/nsChannelToPipeListener.h
@@ -0,0 +1,100 @@
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (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.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla code.
+ *
+ * The Initial Developer of the Original Code is the Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Chris Double <chris.double@double.co.nz>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+#if !defined(nsChannelToPipeListener_h_)
+#define nsChannelToPipeListener_h_
+
+#include "nsCOMPtr.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsIRequestObserver.h"
+#include "nsIStreamListener.h"
+#include "nsIPrincipal.h"
+
+// Constant for download and playback rates that are unknown, or otherwise
+// unable to be computed.
+#define NS_MEDIA_UNKNOWN_RATE -1.0
+
+class nsMediaDecoder;
+
+/* 
+   Reads all data on the input stream of a channel and
+   writes it to a pipe. This allows a seperate thread to
+   read data from a channel running on the main thread
+*/
+class nsChannelToPipeListener : public nsIStreamListener
+{
+  // ISupports
+  NS_DECL_ISUPPORTS
+
+  // IRequestObserver
+  NS_DECL_NSIREQUESTOBSERVER
+
+  // IStreamListener
+  NS_DECL_NSISTREAMLISTENER
+
+  public:
+  nsChannelToPipeListener(nsMediaDecoder* aDecoder);
+  nsresult Init();
+  nsresult GetInputStream(nsIInputStream** aStream);
+  void Stop();
+  void Cancel();
+
+  // Return the download rate in bytes per second. Returns 
+  // less than zero if the download has complated.
+  double BytesPerSecond() const;
+
+  nsIPrincipal* GetCurrentPrincipal();
+
+private:
+  nsCOMPtr<nsIInputStream> mInput;
+  nsCOMPtr<nsIOutputStream> mOutput;
+  nsCOMPtr<nsIPrincipal> mPrincipal;
+  nsMediaDecoder* mDecoder;
+
+  // Interval when download started. Used in
+  // computing bytes per second download rate.
+  PRIntervalTime mIntervalStart;
+
+  // Interval when last downloaded bytes occurred. Used in computer
+  // bytes per second download rate.
+  PRIntervalTime mIntervalEnd;
+
+  // Total bytes transferred so far
+  PRInt64 mTotalBytes;
+};
+
+#endif
new file mode 100644
--- /dev/null
+++ b/content/media/video/public/nsMediaDecoder.h
@@ -0,0 +1,225 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: ML 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (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.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla code.
+ *
+ * The Initial Developer of the Original Code is the Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Chris Double <chris.double@double.co.nz>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+#if !defined(nsMediaDecoder_h_)
+#define nsMediaDecoder_h_
+
+#include "nsIObserver.h"
+#include "nsIPrincipal.h"
+#include "nsSize.h"
+#include "prlog.h"
+#include "gfxContext.h"
+#include "gfxRect.h"
+#include "nsITimer.h"
+
+#ifdef PR_LOGGING
+extern PRLogModuleInfo* gVideoDecoderLog;
+#define LOG(type, msg) PR_LOG(gVideoDecoderLog, type, msg)
+#else
+#define LOG(type, msg)
+#endif
+
+class nsHTMLMediaElement;
+
+// All methods of nsMediaDecoder must be called from the main thread only
+// with the exception of SetRGBData. The latter can be called from any thread.
+class nsMediaDecoder : public nsIObserver
+{
+ public:
+  nsMediaDecoder();
+  virtual ~nsMediaDecoder();
+
+  // Initialize the logging object
+  static nsresult InitLogger();
+
+  // Perform any initialization required for the decoder.
+  // Return PR_TRUE on successful initialisation, PR_FALSE
+  // on failure.
+  virtual PRBool Init();
+
+  // Return the current URI being played or downloaded.
+  virtual void GetCurrentURI(nsIURI** aURI) = 0;
+
+  // Return the principal of the current URI being played or downloaded.
+  virtual nsIPrincipal* GetCurrentPrincipal() = 0;
+
+  // Return the time position in the video stream being
+  // played measured in seconds.
+  virtual float GetCurrentTime() = 0;
+
+  // Seek to the time position in (seconds) from the start of the video.
+  virtual nsresult Seek(float time) = 0;
+
+  // Called by the element when the playback rate has been changed.
+  // Adjust the speed of the playback, optionally with pitch correction,
+  // when this is called.
+  virtual nsresult PlaybackRateChanged() = 0;
+
+  // Return the duration of the video in seconds.
+  virtual float GetDuration() = 0;
+  
+  // Pause video playback.
+  virtual void Pause() = 0;
+
+  // Return the current audio volume that the video plays at. 
+  // This is a value form 0 through to 1.0.
+  virtual float GetVolume() = 0;
+
+  // Set the audio volume. It should be a value from 0 to 1.0.
+  virtual void SetVolume(float volume) = 0;
+
+  // Start playback of a video. 'Load' must have previously been
+  // called.
+  virtual nsresult Play() = 0;
+
+  // Stop playback of a video, and stop download of video stream.
+  virtual void Stop() = 0;
+
+  // Start downloading the video at the given URI. Decode
+  // the downloaded data up to the point of the first frame
+  // of data. 
+  virtual nsresult Load(nsIURI* aURI) = 0;
+
+  // Draw the latest video data. This is done
+  // here instead of in nsVideoFrame so that the lock around the
+  // RGB buffer doesn't have to be exposed publically.
+  // The current video frame is drawn to fill aRect.
+  // Called in the main thread only.
+  virtual void Paint(gfxContext* aContext, const gfxRect& aRect);
+
+  // Called when the video file has completed downloading.
+  virtual void ResourceLoaded() = 0;
+
+  // Call from any thread safely. Return PR_TRUE if we are currently
+  // seeking in the media resource.
+  virtual PRBool IsSeeking() const = 0;
+
+  // Return the current number of bytes loaded from the video file.
+  // This is used for progress events.
+  virtual PRUint32 GetBytesLoaded() = 0;
+
+  // Return the size of the video file in bytes.
+  // This is used for progress events.
+  virtual PRUint32 GetTotalBytes() = 0;
+
+  // Called when the HTML DOM element is bound.
+  virtual void ElementAvailable(nsHTMLMediaElement* anElement);
+
+  // Called when the HTML DOM element is unbound.
+  virtual void ElementUnavailable();
+
+  // Invalidate the frame.
+  virtual void Invalidate();
+
+  // Update progress information.
+  virtual void Progress();
+
+  // Keep track of the number of bytes downloaded
+  virtual void UpdateBytesDownloaded(PRUint32 aBytes) = 0;
+
+protected:
+  // Cleanup internal data structures
+  virtual void Shutdown();
+
+  // Start invalidating the video frame at the interval required
+  // by the specificed framerate (in frames per second).
+  nsresult StartInvalidating(float aFramerate);
+
+  // Stop invalidating the video frame
+  void StopInvalidating();
+
+  // Start timer to update download progress information.
+  nsresult StartProgress();
+
+  // Stop progress information timer.
+  nsresult StopProgress();
+
+  // Called on the main thread when the size of the media data has
+  // changed to inform the element so it can keep a local copy of the
+  // current size.
+  void MediaSizeChanged();
+
+  // Set the RGB width, height and framerate. The passed RGB buffer is
+  // copied to the mRGB buffer. This also allocates the mRGB buffer if
+  // needed.
+  // This is the only nsMediaDecoder method that may be called 
+  // from threads other than the main thread.
+  void SetRGBData(PRInt32 aWidth, 
+                  PRInt32 aHeight, 
+                  float aFramerate, 
+                  unsigned char* aRGBBuffer);
+
+protected:
+  // Timer used for invalidating the video 
+  nsCOMPtr<nsITimer> mInvalidateTimer;
+
+  // Timer used for updating progress events 
+  nsCOMPtr<nsITimer> mProgressTimer;
+
+  // The element is not reference counted. Instead the decoder is
+  // notified when it is able to be used. It should only ever be
+  // accessed from the main thread.
+  nsHTMLMediaElement* mElement;
+
+  // RGB data for last decoded frame of video data.
+  // The size of the buffer is mRGBWidth*mRGBHeight*4 bytes and
+  // contains bytes in RGBA format.
+  nsAutoArrayPtr<unsigned char> mRGB;
+
+  PRInt32 mRGBWidth;
+  PRInt32 mRGBHeight;
+
+  // Has our size changed since the last repaint?
+  PRPackedBool mSizeChanged;
+
+  // Lock around the video RGB, width and size data. This
+  // is used in the decoder backend threads and the main thread
+  // to ensure that repainting the video does not use these
+  // values while they are out of sync (width changed but
+  // not height yet, etc).
+  // Backends that are updating the height, width or writing
+  // to the RGB buffer must obtain this lock first to ensure that
+  // the video element does not use video data or sizes that are
+  // in the midst of being changed.
+  PRLock* mVideoUpdateLock;
+
+  // Framerate of video being displayed in the element
+  // expressed in numbers of frames per second.
+  float mFramerate;
+};
+
+#endif
new file mode 100644
--- /dev/null
+++ b/content/media/video/public/nsMediaStream.h
@@ -0,0 +1,190 @@
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (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.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla code.
+ *
+ * The Initial Developer of the Original Code is the Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Chris Double <chris.double@double.co.nz>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+#if !defined(nsMediaStream_h_)
+#define nsMediaStream_h_
+
+#include "nsCOMPtr.h"
+#include "nsAutoPtr.h"
+#include "nsIChannel.h"
+#include "nsIPrincipal.h"
+#include "nsIURI.h"
+#include "prlock.h"
+
+class nsMediaDecoder;
+
+// An abstract class that implements the low level functionality of nsMediaStream to open, close,
+// read and seek in streams. nsMediaStream constructs a concrete implementation of this class based
+// on the channel type to enable efficient seeking, reading and writing optimized for the channel type.
+class nsStreamStrategy 
+{
+public:
+ nsStreamStrategy(nsMediaDecoder* aDecoder, nsIChannel* aChannel, nsIURI* aURI) :
+    mDecoder(aDecoder),
+    mChannel(aChannel),
+    mURI(aURI),
+    mLock(nsnull)  
+  {
+    MOZ_COUNT_CTOR(nsStreamStrategy);
+    mLock = PR_NewLock();
+  }
+
+  virtual ~nsStreamStrategy()
+  {
+    PR_DestroyLock(mLock);
+    MOZ_COUNT_DTOR(nsStreamStrategy);
+  }
+
+  // These methods have the same thread calling requirements 
+  // as those with the same name in nsMediaStream
+  virtual nsresult Open() = 0;
+  virtual nsresult Close() = 0;
+  virtual nsresult Read(char* aBuffer, PRUint32 aCount, PRUint32* aBytes) = 0;
+  virtual nsresult Seek(PRInt32 aWhence, PRInt64 aOffset) = 0;
+  virtual PRInt64  Tell() = 0;
+  virtual PRUint32 Available() = 0;
+  virtual float    DownloadRate() = 0;
+  virtual void     Cancel() { }
+  virtual nsIPrincipal* GetCurrentPrincipal() = 0;
+
+protected:
+  // This is not an nsCOMPointer to prevent a circular reference
+  // between the decoder to the media stream object. The stream never
+  // outlives the lifetime of the decoder.
+  nsMediaDecoder* mDecoder;
+
+  // Channel used to download the media data. Must be accessed
+  // from the main thread only.
+  nsCOMPtr<nsIChannel> mChannel;
+
+  // URI in case the stream needs to be re-opened. Access from
+  // main thread only.
+  nsCOMPtr<nsIURI> mURI;
+
+  // This lock handles synchronisation between calls to Close() and
+  // the Read, Seek, etc calls. Close must not be called while a
+  // Read or Seek is in progress since it resets various internal
+  // values to null.
+  PRLock* mLock;
+};
+
+/* 
+   Provides the ability to open, read and seek into a media stream
+   (audio, video). Handles the underlying machinery to do range
+   requests, etc as needed by the actual stream type. Instances of
+   this class must be created on the main thread. 
+
+   Open, Close and Cancel must be called on the main thread only. Once
+   the stream is open the remaining methods (except for Close and
+   Cancel) may be called on another thread which may be a non main
+   thread. They may not be called on multiple other threads though. In
+   the case of the Ogg Decoder they are called on the Decode thread
+   for example. You must ensure that no threads are calling these
+   methods once Close is called.
+*/
+class nsMediaStream
+{
+ public:
+  nsMediaStream();
+  ~nsMediaStream();
+
+  // Create a channel for the stream, reading data from the 
+  // media resource at the URI. Call on main thread only.
+  nsresult Open(nsMediaDecoder *aDecoder, nsIURI* aURI);
+
+  // Close the stream, stop any listeners, channels, etc.
+  // Call on main thread only.
+  nsresult Close();
+
+  // Read up to aCount bytes from the stream. The buffer must have
+  // enough room for at least aCount bytes. Stores the number of
+  // actual bytes read in aBytes (0 on end of file). Can be called
+  // from any thread.
+  nsresult Read(char* aBuffer, PRUint32 aCount, PRUint32* aBytes);
+
+  // Seek to the given bytes offset in the stream. aWhence can be
+  // one of:
+  //   NS_SEEK_SET
+  //   NS_SEEK_CUR
+  //   NS_SEEK_END
+  //
+  // Can be called from any thread.
+  nsresult Seek(PRInt32 aWhence, PRInt64 aOffset);
+
+  // Report the current offset in bytes from the start of the stream.
+  // Can be called from any thread.
+  PRInt64 Tell();
+
+  // Return the number of bytes available in the stream that can be
+  // read without blocking. Can be called from any thread.
+  PRUint32 Available();
+
+  // Return the current download rate in bytes per second. Returns less than
+  // zero if the download has completed. Can be called from any
+  // thread.
+  float DownloadRate();
+
+  // Return the current playback rate in bytes per second. Can be
+  // called from any thread.
+  float PlaybackRate();
+
+  // Cancels any currently blocking request and forces that request to
+  // return an error. Call on main thread only.
+  void Cancel();
+
+  // Call on main thread only.
+  nsIPrincipal* GetCurrentPrincipal();
+
+ private:
+  // Strategy object that is used for the handling seeking, etc
+  // Accessed from any thread. Set on the Open call on the main thread
+  // only. Open is always called first on the main thread before any
+  // other calls from other threads.
+  nsAutoPtr<nsStreamStrategy> mStreamStrategy;
+
+  // Time used for computing average playback rate. Written on the 
+  // main thread only during the Open call. Read from any thread during
+  // calls to PlaybackRate() - which can only ever happen after Open.
+  PRIntervalTime mPlaybackRateStart;
+
+  // Bytes downloaded for average playback rate computation. Initialized
+  // on the main thread during Open(). After that it is read and written
+  // possibly on a different thread, but exclusively from that
+  // thread. In the case of the Ogg Decoder, it is the Decoder thread.
+  PRUint32 mPlaybackRateCount;
+};
+
+#endif
--- a/content/media/video/public/nsOggDecoder.h
+++ b/content/media/video/public/nsOggDecoder.h
@@ -36,117 +36,269 @@
  *
  * ***** END LICENSE BLOCK ***** */
 /*
 Each video element has two threads. They are:
   Decode Thread
     This thread owns the resources for downloading
     and reading the video file. It goes through the file, decoding
     the theora and vorbis data. It uses Oggplay to do the decoding.
+    It indirectly uses an nsMediaStream to do the file reading and
+    seeking via Oggplay. All file reads and seeks must occur on this
+    thread only.
     
-  Presentation Thread This thread goes through the data decoded by the
-    decode thread, generates the RGB buffer. If there is audio data it
-    queues it for playing. This thread owns the audio device - all
-    audio operations occur on this thread. The video data is actually
-    displayed by a timer that goes off at the right framerate
-    interval. This timer sends an Invalidate event to the frame.
+  Display Thread 
+    This thread goes through the data decoded by the decode thread,
+    and generates the RGB buffer. If there is audio data it queues it
+    for playing. This thread owns the audio device - all audio
+    operations occur on this thread. Once the RGB buffer is available
+    an event is sent to the main thread to invalidate the frame.
+
+The advantage of using two threads is that when decoding blocks for
+i/o the display thread can continue to update frames from the buffer
+of decoded frames. It also prevents audio glitches and video skipping
+if decoding takes slightly longer than the framerate.
+
+When the threads are created an event is dispatched to it. The event
+runs for the lifetime of the playback of the resource. The events
+synchronise via a single monitor held by the nsOggDecoder object.
+
+Each event contains a Run method which consists of an infinite loop
+that checks the state that the state machine is in and processes
+operations on that state.
+
+The nsOggDecodeStateMachine class is the event that gets dispatched to
+the decode thread. It has the following states:
+
+DECODING_METADATA
+  The Ogg headers are being loaded, and things like framerate, etc are
+  being decoded.  
+DECODING_FIRSTFRAME
+  The first frame of audio/video data is being decoded.
+DECODING
+  Video/Audio frames are being decoded.
+SEEKING
+  A seek operation is in progress.
+BUFFERING
+  Decoding is paused while data is buffered for smooth playback.
+COMPLETED
+  The resource has completed decoding. 
+SHUTDOWN
+  The decoder object is about to be destroyed.
+
+The following result in state transitions.
+
+Shutdown()
+  Clean up any resources the nsOggDecodeStateMachine owns.
+Decode()
+  Start decoding video frames.
+Buffer
+  This is not user initiated. It occurs when the
+  available data in the stream drops below a certain point.
+Complete
+  This is not user initiated. It occurs when the
+  stream is completely decoded.
+Seek(float)
+  Seek to the time position given in the resource.
+
+A state transition diagram:
 
-Operations are performed in the threads by sending Events via the 
-Dispatch method on the thread.
+DECODING_METADATA
+|        | Shutdown()
+  v        >-------------------->--------------------------|
+  |                                                        |
+DECODING_FIRSTFRAME                                        v
+  |        | Shutdown()                                    |
+  v        >-------------------->--------------------------|
+  |  |------------->----->------------------------|        v
+DECODING             |          |  |              |        |
+  ^                  v Seek(t)  |  |              |        |
+  |         Decode() |          v  |              |        |
+  ^-----------<----SEEKING      |  v Complete     v        v
+  |                  |          |  |              |        |
+  |                  |          |  COMPLETED    SHUTDOWN-<-|
+  ^                  ^          |  |Shutdown()    |
+  |                  |          |  >-------->-----^
+  |         Decode() |Seek(t)   |Buffer()         |
+  -----------<--------<-------BUFFERING           |
+                                |                 ^
+                                v Shutdown()      |
+                                |                 |
+                                ------------>-----|
 
-The general sequence of events with this objects is:
+The nsOggDisplayStateMachine class is the event that gets dispatched
+to the display thread. It doesn't have an explicit state but it
+computes its state based on the states of the decode state machine and
+the main play state. It is either playing, paused or finished.
+
+The Main thread controls the above state machines by sending events
+based on the high level player actions required (Seek, Pause, Play, etc).
+
+The player states are the states requested by the client through the
+DOM API.  They represent the desired state of the player, while the
+decoder's state represents the actual state of the decoder.
+
+The high level state of the player is maintained via a PlayState value. 
+It can have the following states:
 
-1) The video element calls Load on nsVideoDecoder. This creates the 
-   threads and starts the channel for downloading the file. It sends
-   an event to the decode thread to load the initial Ogg metadata. 
-   These are the headers that give the video size, framerate, etc.
-   It returns immediately to the calling video element.
+START
+  The decoder has been initialized but has no resource loaded.
+PAUSED
+  A request via the API has been received to pause playback.
+LOADING
+  A request via the API has been received to load a resource.
+PLAYING
+  A request via the API has been received to start playback.
+SEEKING
+  A request via the API has been received to start seeking.
+COMPLETED
+  Playback has completed.
+SHUTDOWN
+  The decoder is about to be destroyed.
+
+State transition occurs when the Media Element calls the Play, Seek,
+etc methods on the nsOggDecoder object. When the transition occurs
+nsOggDecoder then calls the methods on the decoder state machine
+object to cause it to behave appropriate to the play state.
+
+The following represents the states that the player can be in, and the
+valid states the decode thread can be in at that time:
+
+player LOADING   decoder DECODING_METADATA, DECODING_FIRSTFRAME
+player PLAYING   decoder DECODING, BUFFERING, SEEKING, COMPLETED
+player PAUSED    decoder DECODING, BUFFERING, SEEKING, COMPLETED
+player SEEKING   decoder SEEKING
+player COMPLETED decoder SHUTDOWN
+player SHUTDOWN  decoder SHUTDOWN
+
+The general sequence of events with these objects is:
+
+1) The video element calls Load on nsMediaDecoder. This creates the
+   decode thread and starts the channel for downloading the file. It
+   instantiates and starts the Decode state machines. The high level
+   LOADING state is entered, which results in the decode state machine
+   to start decoding metadata. These are the headers that give the
+   video size, framerate, etc.  It returns immediately to the calling
+   video element.
 
 2) When the Ogg metadata has been loaded by the decode thread it will
-   call a method on the video element object to inform it that this step
-   is done, so it can do the required things by the video specification
-   at this stage. 
-
-   It then queues an event to the decode thread to decode the first
-   frame of Ogg data.
+   call a method on the video element object to inform it that this
+   step is done, so it can do the things required by the video
+   specification at this stage. At this point the display thread is
+   started with the display state machine dispatched to it. The
+   decoder then continues to decode the first frame of data.
 
 3) When the first frame of Ogg data has been successfully decoded it
    calls a method on the video element object to inform it that this
    step has been done, once again so it can do the required things by
    the video specification at this stage.
 
-   It then queues an event to the decode thread to enter the standard
-   decoding loop. This loop continuously reads data from the channel
-   and decodes it. It does this by reading the data, decoding it, and 
-   if there is more data to decode, queues itself back to the thread.
+   This results in the high level state changing to PLAYING or PAUSED
+   depending on any user action that may have occurred.
+
+   That transition causes the Display thread to start displaying data
+   if the PLAYING state is in effect and the decoder is DECODING.
+   
+a/v synchronisation is done by a combination of liboggplay and the
+Display state machine. liboggplay ensures that a decoded frame of data
+has both the audio samples and the YUV data for that period of time.
+The display state machine computes the time since the last frame it
+played and enters the IDLE state if it needs to delay until the next
+frame time is reached.
 
-   The final step of the 'first frame event' is to notify a condition
-   variable that we have decoded the first frame. The presentation thread
-   uses this notification to know when to start displaying video.
+Ideally a/v sync would take into account the actual audio clock of the
+audio hardware for the sync rather than computing the time between
+HandleVideoData calls. Unfortunately getting valid time data out of
+the audio hardware has proven to be unreliable across platforms (and
+even distributions in Linux) depending on audio hardware, audio
+backend etc. The current approach works fine in practice and is a
+compromise until this issue can be sorted.
+
+Shutdown needs to ensure that all events posted to the decode and
+display threads are cleanly completed. The decode thread can
+potentially block internally inside liboggplay when reading, seeking,
+or its internal buffers containing decoded data are full. When blocked
+in this manner a call from the main thread to Shutdown() will hang.
 
-4) At some point the video element calls Play() on the decoder object.
-   This queues an event to the presentation thread which goes through
-   the decoded data, displaying it if it is video, or playing it if it
-   is audio. 
+This is fixed with a protocol to ensure that the decode event cleanly
+completes. The nsMediaStream that the nsChannelReader uses has a
+Cancel() method. Calling this before Shutdown() will close any
+internal streams or listeners resulting in blocked i/o completing with
+an error, and all future i/o on the stream having an error.
+
+This causes the decode thread to exit and Shutdown() can occur.
+
+If the decode thread is seeking then the same Cancel() operation
+causes an error to be returned from the seek call to liboggplay which
+exits out of the seek operation, and stops the seek state running on the
+decode thread.
 
-   Before starting this event will wait on the condition variable 
-   indicating if the first frame has decoded. 
+If the decode thread is blocked due to internal decode buffers being
+full, it is unblocked during the shutdown process by calling
+oggplay_prepare_for_close.
 
-Pausing is handled by stopping the presentation thread. Resuming is 
-handled by restarting the presentation thread. The decode thread never
-gets too far ahead as it is throttled by the Oggplay library.
+To reduce the chance of the internal decode buffer filling and
+blocking, the return value of the oggplay decode step tells us if the
+buffer is full and will block on the next call. If this is the case we
+Wait() on the monitor. The display thread Notifies the monitor when it
+has displayed a frame allowing us to call the decode again without
+blocking.
+
 */
-#if !defined(nsOggDecoder_h___)
-#define nsOggDecoder_h___
+#if !defined(nsOggDecoder_h_)
+#define nsOggDecoder_h_
 
 #include "nsISupports.h"
 #include "nsCOMPtr.h"
 #include "nsIThread.h"
 #include "nsIChannel.h"
 #include "nsChannelReader.h"
 #include "nsIObserver.h"
 #include "nsIFrame.h"
 #include "nsAutoPtr.h"
 #include "nsSize.h"
-#include "prlock.h"
-#include "prcvar.h"
 #include "prlog.h"
+#include "prmon.h"
 #include "gfxContext.h"
 #include "gfxRect.h"
 #include "oggplay/oggplay.h"
-#include "nsVideoDecoder.h"
+#include "nsMediaDecoder.h"
 
 class nsAudioStream;
-class nsVideoDecodeEvent;
-class nsVideoPresentationEvent;
-class nsChannelToPipeListener;
+class nsOggDecodeStateMachine;
+class nsOggDisplayStateMachine;
 
-class nsOggDecoder : public nsVideoDecoder
+class nsOggDecoder : public nsMediaDecoder
 {
-  friend class nsVideoDecodeEvent;
-  friend class nsVideoPresentationEvent;
-  friend class nsChannelToPipeListener;
+  friend class nsOggDecodeStateMachine;
+  friend class nsOggDisplayStateMachine;
 
   // ISupports
   NS_DECL_ISUPPORTS
 
   // nsIObserver
   NS_DECL_NSIOBSERVER
 
  public:
+  // Enumeration for the valid play states (see mPlayState)
+  enum PlayState {
+    PLAY_STATE_START,
+    PLAY_STATE_LOADING,
+    PLAY_STATE_PAUSED,
+    PLAY_STATE_PLAYING,
+    PLAY_STATE_SEEKING,
+    PLAY_STATE_ENDED,
+    PLAY_STATE_SHUTDOWN
+  };
+
   nsOggDecoder();
+  ~nsOggDecoder();
   PRBool Init();
   void Shutdown();
-  ~nsOggDecoder();
   
-  // Returns the current video frame width and height.
-  // If there is no video frame, returns the given default size.
-  nsIntSize GetVideoSize(nsIntSize defaultSize);
-  double GetVideoFramerate();
-
   float GetCurrentTime();
 
   // Start downloading the video at the given URI. Decode
   // the downloaded data up to the point of the first frame
   // of data. 
   nsresult Load(nsIURI* aURI);
 
   // Start playback of a video. 'Load' must have previously been
@@ -166,107 +318,60 @@ class nsOggDecoder : public nsVideoDecod
   void SetVolume(float volume);
   float GetDuration();
 
   void GetCurrentURI(nsIURI** aURI);
   nsIPrincipal* GetCurrentPrincipal();
 
   virtual void UpdateBytesDownloaded(PRUint32 aBytes);
 
-protected:
-  /******
-   * The following methods must only be called on the presentation
-   * thread.
-   ******/
-
-  // Find and render the first frame of video data. Call on 
-  // presentation thread only.
-  void DisplayFirstFrame();
-
-  // Process one frame of video/audio data.
-  // Call on presentation thread only.
-  PRBool StepDisplay();
+  // Called when the video file has completed downloading.
+  // Call on the main thread only.
+  void ResourceLoaded();
 
-  // Process audio or video from one track of the Ogg stream.
-  // Call on presentation thread only.
-  void ProcessTrack(int aTrackNumber, OggPlayCallbackInfo* aTrackInfo);
-
-  // Return the time in seconds that the video display is
-  // synchronised to. This can be based on the current time of
-  // the audio buffer if available, or the system clock. Call
-  // on presentation thread only.
-  double GetSyncTime();
+  // Call from any thread safely. Return PR_TRUE if we are currently
+  // seeking in the media resource.
+  virtual PRBool IsSeeking() const;
 
-  // Return true if the video is currently paused. Call on the
-  // presentation thread only.
-  PRBool IsPaused();
-
-  // Process the video/audio data. Call on presentation thread only. 
-  void HandleVideoData(int track_num, OggPlayVideoData* video_data);
-  void HandleAudioData(OggPlayAudioData* audio_data, int size);
-
-  // Pause the audio. Call on presentation thread only.
-  void DoPause();
+protected:
+  // Change to a new play state. This updates the mState variable and
+  // notifies any thread blocking on this objects monitor of the
+  // change. Can be called on any thread.
+  void ChangeState(PlayState aState);
 
-  // Initializes and opens the audio stream. Call from the
-  // presentation thread only.
-  void OpenAudioStream();
-
-  // Closes and releases resources used by the audio stream.
-  // Call from the presentation thread only.
-  void CloseAudioStream();
-
-  // Initializes the resources owned by the presentation thread,.
-  // Call from the presentation thread only.
-  void StartPresentationThread();
-
-  /******
-   * The following methods must only be called on the decode
-   * thread.
-   ******/
+  // Returns the monitor for other threads to synchronise access to
+  // state
+  PRMonitor* GetMonitor() 
+  { 
+    return mMonitor; 
+  }
 
-  // Loads the header information from the ogg resource and
-  // stores the information about framerate, etc in member
-  // variables. Must be called from the decoder thread only.
-  void LoadOggHeaders();
-
-  // Loads the First frame of the video data, making it available
-  // in the RGB buffer. Must be called from the decoder thread only.
-  void LoadFirstFrame();
-
-  // Decode some data from the media file. This is placed in a 
-  // buffer that is used by the presentation thread. Must
-  // be called from the decoder thread only.
-  PRBool StepDecoding();
-
-  // Ensure that there is enough data buffered from the video 
-  // that we can have a reasonable playback experience. Must
-  // be called from the decoder thread only.
-  void BufferData();
+  // Return the current state. The caller must have entered the
+  // monitor.
+  PlayState GetState()
+  {
+    return mPlayState;
+  }
 
   /****** 
    * The following methods must only be called on the main
    * thread.
    ******/
 
   // Called when the metadata from the Ogg file has been read.
   // Call on the main thread only.
   void MetadataLoaded();
 
   // Called when the first frame has been loaded.
   // Call on the main thread only.
   void FirstFrameLoaded();
 
-  // Called when the video file has completed downloading.
-  // Call on the main thread only.
-  void ResourceLoaded();
-
   // Called when the video has completed playing.
   // Call on the main thread only.
-  void PlaybackCompleted();
+  void PlaybackEnded();
 
   // Return the current number of bytes loaded from the video file.
   // This is used for progress events.
   virtual PRUint32 GetBytesLoaded();
 
   // Return the size of the video file in bytes.
   // This is used for progress events.
   virtual PRUint32 GetTotalBytes();
@@ -274,130 +379,98 @@ protected:
   // Buffering of data has stopped. Inform the element on the main
   // thread.
   void BufferingStopped();
 
   // Buffering of data has started. Inform the element on the main
   // thread.
   void BufferingStarted();
 
-private:
-  // Starts the threads and timers that handle displaying the playing
-  // video and invalidating the frame. Called on the main thread only.
-  void StartPlaybackThreads();
+  // Seeking has stopped. Inform the element on the main
+  // thread.
+  void SeekingStopped();
 
-  /******
-   * The following member variables can be accessed from the
-   * decoding thread only.
-   ******/
+  // Seeking has started. Inform the element on the main
+  // thread.
+  void SeekingStarted();
 
-  // Total number of bytes downloaded so far. 
-  PRUint32 mBytesDownloaded;
+private:
+  // Register/Unregister with Shutdown Observer. 
+  // Call on main thread only.
+  void RegisterShutdownObserver();
+  void UnregisterShutdownObserver();
+
 
   /******
    * The following members should be accessed on the main thread only
    ******/
-  nsCOMPtr<nsIChannel> mChannel;
-  nsCOMPtr<nsChannelToPipeListener> mListener;
+  // Total number of bytes downloaded so far. 
+  PRUint32 mBytesDownloaded;
 
   // The URI of the current resource
   nsCOMPtr<nsIURI> mURI;
 
-  // The audio stream resource. It should only be accessed from
-  // the presentation thread.
-  nsAutoPtr<nsAudioStream> mAudioStream;
+  // Threads to handle decoding of Ogg data.
+  nsCOMPtr<nsIThread> mDecodeThread;
+  nsCOMPtr<nsIThread> mDisplayThread;
 
-  // The time that the next video frame should be displayed in
-  // seconds. This is referenced from 0.0 which is the initial start
-  // of the video stream.
-  double mVideoNextFrameTime;
+  // Volume that playback should start at.  0.0 = muted. 1.0 = full
+  // volume.  Readable/Writeable from the main thread. Read from the
+  // audio thread when it is first started to get the initial volume
+  // level.
+  float mInitialVolume;
 
-  // A load of the media resource is currently in progress. It is 
-  // complete when the media metadata is loaded.
-  PRPackedBool mLoadInProgress;
-
-  // A boolean that indicates that once the load has completed loading
-  // the metadata then it should start playing.
-  PRPackedBool mPlayAfterLoad;
+  // Position to seek to when the seek notification is received by the
+  // decoding thread. Written by the main thread and read via the
+  // decoding thread. Synchronised using mPlayStateMonitor. If the
+  // value is negative then no seek has been requested. When a seek is
+  // started this is reset to negative.
+  float mSeekTime;
 
   // True if we are registered with the observer service for shutdown.
   PRPackedBool mNotifyOnShutdown;
 
   /******
    * The following member variables can be accessed from any thread.
    ******/
-  
-  // Threads to handle decoding of Ogg data. Methods on these are
-  // called from the main thread only, but they can be read from other
-  // threads safely to see if they have been created/set.
-  nsCOMPtr<nsIThread> mDecodeThread;
-  nsCOMPtr<nsIThread> mPresentationThread;
-
-  // Events for doing the Ogg decoding, displaying and repainting on
-  // different threads. These are created on the main thread and are
-  // dispatched to other threads. Threads only access them to dispatch
-  // the event to other threads.
-  nsCOMPtr<nsVideoDecodeEvent> mDecodeEvent;
-  nsCOMPtr<nsVideoPresentationEvent> mPresentationEvent;
-
-  // The time of the current frame from the video stream in
-  // seconds. This is referenced from 0.0 which is the initial start
-  // of the video stream. Set by the presentation thread, and
-  // read-only from the main thread to get the current time value.
-  float mVideoCurrentFrameTime;
 
-  // Volume that playback should start at.  0.0 = muted. 1.0 = full
-  // volume.  Readable/Writeable from the main thread. Read from the
-  // audio thread when it is first started to get the initial volume
-  // level.
-  double mInitialVolume;
+  // The state machine object for handling the decoding via
+  // oggplay. It is safe to call methods of this object from other
+  // threads. Its internal data is synchronised on a monitor. The
+  // lifetime of this object is after mPlayState is LOADING and before
+  // mPlayState is SHUTDOWN. It is safe to access it during this
+  // period.
+  nsCOMPtr<nsOggDecodeStateMachine> mDecodeStateMachine;
 
-  // Audio data. These are initially set on the Decoder thread when
-  // the metadata is loaded. They are read from the presentation
-  // thread after this.
-  PRInt32 mAudioRate;
-  PRInt32 mAudioChannels;
-  PRInt32 mAudioTrack;
-
-  // Video data. Initially set on the Decoder thread when the metadata
-  // is loaded. Read from the presentation thread after this.
-  PRInt32 mVideoTrack;
-
-  // liboggplay State. Passed to liboggplay functions on any
-  // thread. liboggplay handles a lock internally for this.
-  OggPlay* mPlayer;
-
+  // The state machine object for handling the display via oggplay.
+  // It is safe to call methods of this object from other threads. Its
+  // internal data is synchronised on a monitor. The lifetime of this
+  // object is after mPlayState is LOADING and before mPlayState is
+  // SHUTDOWN. It is safe to access it during this period.
+  nsCOMPtr<nsOggDisplayStateMachine> mDisplayStateMachine;
+  
   // OggPlay object used to read data from a channel. Created on main
   // thread. Passed to liboggplay and the locking for multithreaded
-  // access is handled by that library.
+  // access is handled by that library. Some methods are called from
+  // the decoder thread, and the state machine for that thread keeps
+  // a pointer to this reader. This is safe as the only methods called
+  // are threadsafe (via the threadsafe nsMediaStream).
   nsChannelReader* mReader;
 
-  // True if the video playback is paused. Read/Write from the main
-  // thread. Read from the decoder thread to not buffer data if
-  // paused.
-  PRPackedBool mPaused;
-
-  // True if the first frame of data has been loaded. This member,
-  // along with the condition variable and lock is used by threads 
-  // that need to wait for the first frame to be loaded before 
-  // performing some action. In particular it is used for 'autoplay' to
-  // start playback on loading of the first frame.
-  PRPackedBool mFirstFrameLoaded;
-  PRCondVar* mFirstFrameCondVar;
-  PRLock* mFirstFrameLock;
+  // Monitor for detecting when the video play state changes. A call
+  // to Wait on this monitor will block the thread until the next
+  // state change.
+  PRMonitor* mMonitor;
 
-  // System time in seconds since video start, or last pause/resume.
-  // Used for synching video framerate to the system clock if there is
-  // no audio hardware or no audio track. Written by main thread, read
-  // by presentation thread to handle frame rate synchronisation.
-  double mSystemSyncSeconds;
+  // Set to one of the valid play states. It is protected by the
+  // monitor mMonitor. This monitor must be acquired when reading or
+  // writing the state. Any change to the state must call NotifyAll on
+  // the monitor.
+  PlayState mPlayState;
 
-  // The media resource has been completely loaded into the pipe. No
-  // need to attempt to buffer if it starves. Written on the main
-  // thread, read from the decoding thread.
-  PRPackedBool mResourceLoaded;
-
-  // PR_TRUE if the metadata for the video has been loaded. Written on
-  // the main thread, read from the decoding thread.
-  PRPackedBool mMetadataLoaded;
+  // The state to change to after a seek or load operation. It is
+  // protected by the monitor mMonitor. This monitor must be acquired
+  // when reading or writing the state. Any change to the state must
+  // call NotifyAll on the monitor.
+  PlayState mNextState;
 };
 
 #endif
deleted file mode 100644
--- a/content/media/video/public/nsVideoDecoder.h
+++ /dev/null
@@ -1,219 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim:set ts=2 sw=2 sts=2 et cindent: */
-/* ***** BEGIN LICENSE BLOCK *****
- * Version: ML 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (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.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is Mozilla code.
- *
- * The Initial Developer of the Original Code is the Mozilla Corporation.
- * Portions created by the Initial Developer are Copyright (C) 2007
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *  Chris Double <chris.double@double.co.nz>
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either the GNU General Public License Version 2 or later (the "GPL"), or
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
-#if !defined(nsVideoDecoder_h___)
-#define nsVideoDecoder_h___
-
-#include "nsIObserver.h"
-#include "nsSize.h"
-#include "prlog.h"
-#include "gfxContext.h"
-#include "gfxRect.h"
-#include "nsITimer.h"
-
-#ifdef PR_LOGGING
-extern PRLogModuleInfo* gVideoDecoderLog;
-#define LOG(type, msg) PR_LOG(gVideoDecoderLog, type, msg)
-#else
-#define LOG(type, msg)
-#endif
-
-class nsHTMLMediaElement;
-
-// All methods of nsVideoDecoder must be called from the main thread only
-// with the exception of SetRGBData. The latter can be called from any thread.
-class nsVideoDecoder : public nsIObserver
-{
- public:
-  nsVideoDecoder();
-  virtual ~nsVideoDecoder() { }
-
-  // Initialize the logging object
-  static nsresult InitLogger();
-
-  // Perform any initialization required for the decoder.
-  // Return PR_TRUE on successful initialisation, PR_FALSE
-  // on failure.
-  virtual PRBool Init();
-
-  // Return the current URI being played or downloaded.
-  virtual void GetCurrentURI(nsIURI** aURI) = 0;
-
-  // Return the principal of the current URI being played or downloaded.
-  virtual nsIPrincipal* GetCurrentPrincipal() = 0;
-
-  // Return the time position in the video stream being
-  // played measured in seconds.
-  virtual float GetCurrentTime() = 0;
-
-  // Seek to the time position in (seconds) from the start of the video.
-  virtual nsresult Seek(float time) = 0;
-
-  // Called by the element when the playback rate has been changed.
-  // Adjust the speed of the playback, optionally with pitch correction,
-  // when this is called.
-  virtual nsresult PlaybackRateChanged() = 0;
-
-  // Return the duration of the video in seconds.
-  virtual float GetDuration() = 0;
-  
-  // Pause video playback.
-  virtual void Pause() = 0;
-
-  // Return the current audio volume that the video plays at. 
-  // This is a value form 0 through to 1.0.
-  virtual float GetVolume() = 0;
-
-  // Set the audio volume. It should be a value from 0 to 1.0.
-  virtual void SetVolume(float volume) = 0;
-
-  // Returns the current video frame width and height.
-  // If there is no video frame, returns the given default size.
-  virtual nsIntSize GetVideoSize(nsIntSize defaultSize) = 0;
-
-  // Return the current framerate of the video, in frames per second.
-  virtual double GetVideoFramerate() = 0;
-
-  // Start playback of a video. 'Load' must have previously been
-  // called.
-  virtual nsresult Play() = 0;
-
-  // Stop playback of a video, and stop download of video stream.
-  virtual void Stop() = 0;
-
-  // Start downloading the video at the given URI. Decode
-  // the downloaded data up to the point of the first frame
-  // of data. 
-  virtual nsresult Load(nsIURI* aURI) = 0;
-
-  // Draw the latest video data. This is done
-  // here instead of in nsVideoFrame so that the lock around the
-  // RGB buffer doesn't have to be exposed publically.
-  // The current video frame is drawn to fill aRect.
-  // Called in the main thread only.
-  virtual void Paint(gfxContext* aContext, const gfxRect& aRect);
-
-  // Called when the video file has completed downloading.
-  virtual void ResourceLoaded() = 0;
-
-  // Return the current number of bytes loaded from the video file.
-  // This is used for progress events.
-  virtual PRUint32 GetBytesLoaded() = 0;
-
-  // Return the size of the video file in bytes.
-  // This is used for progress events.
-  virtual PRUint32 GetTotalBytes() = 0;
-
-  // Called when the HTML DOM element is bound.
-  virtual void ElementAvailable(nsHTMLMediaElement* anElement);
-
-  // Called when the HTML DOM element is unbound.
-  virtual void ElementUnavailable();
-
-  // Invalidate the frame.
-  virtual void Invalidate();
-
-  // Update progress information.
-  virtual void Progress();
-
-protected:
-  // Cleanup internal data structures
-  virtual void Shutdown();
-
-  // Start invalidating the video frame at the interval required
-  // by the specificed framerate (in frames per second).
-  nsresult StartInvalidating(double aFramerate);
-
-  // Stop invalidating the video frame
-  void StopInvalidating();
-
-  // Start timer to update download progress information.
-  nsresult StartProgress();
-
-  // Stop progress information timer.
-  nsresult StopProgress();
-
-  // Set the RGB width, height and framerate. The passed RGB buffer is
-  // copied to the mRGB buffer. This also allocates the mRGB buffer if
-  // needed.
-  // This is the only nsVideoDecoder method that may be called 
-  // from threads other than the main thread.
-  void SetRGBData(PRInt32 aWidth, 
-                  PRInt32 aHeight, 
-                  double aFramerate, 
-                  unsigned char* aRGBBuffer);
-
-protected:
-  // Timer used for invalidating the video 
-  nsCOMPtr<nsITimer> mInvalidateTimer;
-
-  // Timer used for updating progress events 
-  nsCOMPtr<nsITimer> mProgressTimer;
-
-  // The element is not reference counted. Instead the decoder is
-  // notified when it is able to be used. It should only ever be
-  // accessed from the main thread.
-  nsHTMLMediaElement* mElement;
-
-  // RGB data for last decoded frame of video data.
-  // The size of the buffer is mRGBWidth*mRGBHeight*4 bytes and
-  // contains bytes in RGBA format.
-  nsAutoArrayPtr<unsigned char> mRGB;
-
-  PRInt32 mRGBWidth;
-  PRInt32 mRGBHeight;
-
-  // Has our size changed since the last repaint?
-  PRPackedBool mSizeChanged;
-
-  // Lock around the video RGB, width and size data. This
-  // is used in the decoder backend threads and the main thread
-  // to ensure that repainting the video does not use these
-  // values while they are out of sync (width changed but
-  // not height yet, etc).
-  // Backends that are updating the height, width or writing
-  // to the RGB buffer must obtain this lock first to ensure that
-  // the video element does not use video data or sizes that are
-  // in the midst of being changed.
-  PRLock* mVideoUpdateLock;
-
-  // Framerate of video being displayed in the element
-  // expressed in numbers of frames per second.
-  double mFramerate;
-};
-
-#endif
--- a/content/media/video/src/Makefile.in
+++ b/content/media/video/src/Makefile.in
@@ -84,23 +84,25 @@ REQUIRES	= \
 		uconv \
 		intl \
 		plugin \
 		cairo \
 		libpixman \
 		$(NULL)
 
 CPPSRCS		= \
-		nsVideoDecoder.cpp \
+		nsMediaDecoder.cpp \
 		$(NULL)
 
 ifdef MOZ_OGG
 CPPSRCS		+= \
 		nsAudioStream.cpp \
 		nsChannelReader.cpp \
+		nsChannelToPipeListener.cpp \
+		nsMediaStream.cpp \
 		nsOggDecoder.cpp \
 		$(NULL)
 endif
 
 FORCE_STATIC_LIB = 1
 
 include $(topsrcdir)/config/rules.mk
 
--- a/content/media/video/src/nsChannelReader.cpp
+++ b/content/media/video/src/nsChannelReader.cpp
@@ -34,177 +34,81 @@
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 #include "nsAString.h"
 #include "nsThreadUtils.h"
 #include "nsNetUtil.h"
 #include "prlog.h"
-#include "nsOggDecoder.h"
+#include "nsMediaDecoder.h"
 #include "nsChannelReader.h"
 #include "nsIScriptSecurityManager.h"
 
-nsChannelToPipeListener::nsChannelToPipeListener(nsOggDecoder* aDecoder) :
-  mDecoder(aDecoder),
-  mIntervalStart(0),
-  mIntervalEnd(0),
-  mTotalBytes(0)
-{
-}
-
-nsresult nsChannelToPipeListener::Init() 
-{
-  nsresult rv = NS_NewPipe(getter_AddRefs(mInput), 
-                           getter_AddRefs(mOutput),
-                           0, 
-                           PR_UINT32_MAX);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  return NS_OK;
-}
-
-void nsChannelToPipeListener::Stop()
-{
-  mDecoder = nsnull;
-  mInput = nsnull;
-  mOutput = nsnull;
-  mDecoder = nsnull;
-}
-
-double nsChannelToPipeListener::BytesPerSecond() const
-{
-  return mTotalBytes / ((PR_IntervalToMilliseconds(mIntervalEnd-mIntervalStart)) / 1000.0);
-}
-
-void nsChannelToPipeListener::GetInputStream(nsIInputStream** aStream)
-{
-  if (aStream) {
-    NS_IF_ADDREF(*aStream = mInput);
-  }
-}
-
-nsresult nsChannelToPipeListener::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
+void nsChannelReader::Cancel()
 {
-  mIntervalStart = PR_IntervalNow();
-  mIntervalEnd = mIntervalStart;
-  mTotalBytes = 0;
-  mDecoder->UpdateBytesDownloaded(mTotalBytes);
-
-  /* Get our principal */
-  nsCOMPtr<nsIChannel> chan(do_QueryInterface(aRequest));
-  if (chan) {
-    nsCOMPtr<nsIScriptSecurityManager> secMan =
-      do_GetService("@mozilla.org/scriptsecuritymanager;1");
-    if (secMan) {
-      nsresult rv = secMan->GetChannelPrincipal(chan,
-                                                getter_AddRefs(mPrincipal));
-      if (NS_FAILED(rv)) {
-        return rv;
-      }
-    }
-  }
-
-  return NS_OK;
-}
-
-nsresult nsChannelToPipeListener::OnStopRequest(nsIRequest* aRequest, nsISupports* aContext, nsresult aStatus) 
-{
-  mOutput = nsnull;
-  if (mDecoder) {
-    mDecoder->ResourceLoaded();
-  }
-  return NS_OK;
+  mStream.Cancel();
 }
 
-nsresult nsChannelToPipeListener::OnDataAvailable(nsIRequest* aRequest, 
-                                                nsISupports* aContext, 
-                                                nsIInputStream* aStream,
-                                                PRUint32 aOffset,
-                                                PRUint32 aCount)
-{
-  if (mOutput) {
-    PRUint32 bytes = 0;
-  
-    do {
-      nsresult rv = mOutput->WriteFrom(aStream, aCount, &bytes);
-      NS_ENSURE_SUCCESS(rv, rv);
-      
-      aCount -= bytes;
-      mTotalBytes += bytes;
-      mDecoder->UpdateBytesDownloaded(mTotalBytes);
-    } while (aCount) ;
-    
-    nsresult rv = mOutput->Flush();
-    NS_ENSURE_SUCCESS(rv, rv);
-    mIntervalEnd = PR_IntervalNow();
-  }
-  return NS_OK;
-}
-
-nsIPrincipal*
-nsChannelToPipeListener::GetCurrentPrincipal()
-{
-  return mPrincipal;
-}
-
-NS_IMPL_ISUPPORTS2(nsChannelToPipeListener, nsIRequestObserver, nsIStreamListener)
-
 PRUint32 nsChannelReader::Available()
 {
-  PRUint32 available = 0;
-  mInput->Available(&available);
-  return available;
+  return mStream.Available();
 }
 
-double nsChannelReader::BytesPerSecond() const
+float nsChannelReader::DownloadRate()
 {
-  return mListener->BytesPerSecond();
+  return mStream.DownloadRate();
+}
+
+float nsChannelReader::PlaybackRate()
+{
+  return mStream.PlaybackRate();
 }
 
 OggPlayErrorCode nsChannelReader::initialise(int aBlock)
 {
   return E_OGGPLAY_OK;
 }
 
 OggPlayErrorCode nsChannelReader::destroy()
 {
-  mChannel->Cancel(NS_BINDING_ABORTED);
-  mChannel = nsnull;
-  mInput->Close();
-  mInput = nsnull;
-  mListener->Stop();
-  mListener = nsnull;
+  mStream.Close();
 
   return E_OGGPLAY_OK;
 }
 
 size_t nsChannelReader::io_read(char* aBuffer, size_t aCount)
 {
   PRUint32 bytes = 0;
-  nsresult rv = mInput->Read(aBuffer, aCount, &bytes);
+  nsresult rv = mStream.Read(aBuffer, aCount, &bytes);
   if (!NS_SUCCEEDED(rv)) {
-    bytes = 0;
+    return static_cast<size_t>(OGGZ_ERR_SYSTEM);
   }
 
   mCurrentPosition += bytes;
-  return bytes == 0 ? static_cast<size_t>(OGGZ_ERR_SYSTEM) : bytes;
+  return bytes;
 }
 
 int nsChannelReader::io_seek(long aOffset, int aWhence)
 {
-  // How to implement seek in channels?
-  // fseek(file, offset, whence);
-
-  return -1;
+  nsresult rv = mStream.Seek(aWhence, aOffset);
+  if (NS_SUCCEEDED(rv))
+    return aOffset;
+  
+  return OGGZ_STOP_ERR;
 }
 
 long nsChannelReader::io_tell()
 {
-  return mCurrentPosition;
+  return mStream.Tell();
+}
+
+int nsChannelReader::duration()
+{
+  return 3600000; // TODO: implement correctly
 }
 
 static OggPlayErrorCode oggplay_channel_reader_initialise(OggPlayReader* aReader, int aBlock) 
 {
   nsChannelReader * me = static_cast<nsChannelReader*>(aReader);
 
   if (me == NULL) {
     return E_OGGPLAY_BAD_READER;
@@ -233,53 +137,43 @@ static int oggplay_channel_reader_io_see
 }
 
 static long oggplay_channel_reader_io_tell(void* aReader) 
 {
   nsChannelReader* me = static_cast<nsChannelReader*>(aReader);
   return me->io_tell();
 }
 
-nsresult nsChannelReader::Init(nsOggDecoder* aDecoder, nsIURI* aURI)
+static int oggplay_channel_reader_duration(OggPlayReader* aReader) 
 {
-  nsresult rv;
-
-  mCurrentPosition = 0;
-  mListener = new nsChannelToPipeListener(aDecoder);
-  NS_ENSURE_TRUE(mListener, NS_ERROR_OUT_OF_MEMORY);
+  nsChannelReader* me = static_cast<nsChannelReader*>(aReader);
+  return me->duration();
+}
 
-  rv = mListener->Init();
-  NS_ENSURE_SUCCESS(rv, rv);
-  
-  rv = NS_NewChannel(getter_AddRefs(mChannel), 
-                     aURI, 
-                     nsnull,
-                     nsnull,
-                     nsnull,
-                     nsIRequest::LOAD_NORMAL);
-  NS_ENSURE_SUCCESS(rv, rv);
+nsresult nsChannelReader::Init(nsMediaDecoder* aDecoder, nsIURI* aURI)
+{
+  mCurrentPosition = 0;
+  return mStream.Open(aDecoder, aURI);
+}
 
-  rv = mChannel->AsyncOpen(mListener, nsnull);
-  NS_ENSURE_SUCCESS(rv, rv);
-  
-  mListener->GetInputStream(getter_AddRefs(mInput));
-
-  return NS_OK;
+nsChannelReader::~nsChannelReader()
+{
+  MOZ_COUNT_DTOR(nsChannelReader);
 }
 
 nsChannelReader::nsChannelReader() 
 {
+  MOZ_COUNT_CTOR(nsChannelReader);
   OggPlayReader* reader = this;
   reader->initialise = &oggplay_channel_reader_initialise;
   reader->destroy = &oggplay_channel_reader_destroy;
   reader->seek = nsnull;
-  reader->io_read = &oggplay_channel_reader_io_read;
-  reader->io_seek = &oggplay_channel_reader_io_seek;
-  reader->io_tell = &oggplay_channel_reader_io_tell;
+  reader->io_read  = &oggplay_channel_reader_io_read;
+  reader->io_seek  = &oggplay_channel_reader_io_seek;
+  reader->io_tell  = &oggplay_channel_reader_io_tell;
+  reader->duration = &oggplay_channel_reader_duration;
 }
 
 nsIPrincipal*
 nsChannelReader::GetCurrentPrincipal()
 {
-  if (!mListener)
-    return nsnull;
-  return mListener->GetCurrentPrincipal();
+  return mStream.GetCurrentPrincipal();
 }
new file mode 100644
--- /dev/null
+++ b/content/media/video/src/nsChannelToPipeListener.cpp
@@ -0,0 +1,156 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (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.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla code.
+ *
+ * The Initial Developer of the Original Code is the Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Chris Double <chris.double@double.co.nz>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+#include "nsAString.h"
+#include "nsNetUtil.h"
+#include "nsMediaDecoder.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsChannelToPipeListener.h"
+
+nsChannelToPipeListener::nsChannelToPipeListener(nsMediaDecoder* aDecoder) :
+  mDecoder(aDecoder),
+  mIntervalStart(0),
+  mIntervalEnd(0),
+  mTotalBytes(0)
+{
+}
+
+nsresult nsChannelToPipeListener::Init() 
+{
+  nsresult rv = NS_NewPipe(getter_AddRefs(mInput), 
+                           getter_AddRefs(mOutput),
+                           0, 
+                           PR_UINT32_MAX);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+void nsChannelToPipeListener::Stop()
+{
+  mDecoder = nsnull;
+  mInput = nsnull;
+  mOutput = nsnull;
+}
+
+void nsChannelToPipeListener::Cancel()
+{
+  if (mOutput)
+    mOutput->Close();
+
+  if (mInput)
+    mInput->Close();
+}
+
+double nsChannelToPipeListener::BytesPerSecond() const
+{
+  return mOutput ? mTotalBytes / ((PR_IntervalToMilliseconds(mIntervalEnd-mIntervalStart)) / 1000.0) : NS_MEDIA_UNKNOWN_RATE;
+}
+
+nsresult nsChannelToPipeListener::GetInputStream(nsIInputStream** aStream)
+{
+  NS_IF_ADDREF(*aStream = mInput);
+  return NS_OK;
+}
+
+nsresult nsChannelToPipeListener::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
+{
+  mIntervalStart = PR_IntervalNow();
+  mIntervalEnd = mIntervalStart;
+  mTotalBytes = 0;
+  mDecoder->UpdateBytesDownloaded(mTotalBytes);
+
+  /* Get our principal */
+  nsCOMPtr<nsIChannel> chan(do_QueryInterface(aRequest));
+  if (chan) {
+    nsCOMPtr<nsIScriptSecurityManager> secMan =
+      do_GetService("@mozilla.org/scriptsecuritymanager;1");
+    if (secMan) {
+      nsresult rv = secMan->GetChannelPrincipal(chan,
+                                                getter_AddRefs(mPrincipal));
+      if (NS_FAILED(rv)) {
+        return rv;
+      }
+    }
+  }
+
+  return NS_OK;
+}
+
+nsresult nsChannelToPipeListener::OnStopRequest(nsIRequest* aRequest, nsISupports* aContext, nsresult aStatus) 
+{
+  mOutput = nsnull;
+  if (mDecoder) {
+    mDecoder->ResourceLoaded();
+  }
+  return NS_OK;
+}
+
+nsresult nsChannelToPipeListener::OnDataAvailable(nsIRequest* aRequest, 
+                                                nsISupports* aContext, 
+                                                nsIInputStream* aStream,
+                                                PRUint32 aOffset,
+                                                PRUint32 aCount)
+{
+  if (!mOutput)
+    return NS_ERROR_FAILURE;
+
+  PRUint32 bytes = 0;
+  
+  do {
+    nsresult rv = mOutput->WriteFrom(aStream, aCount, &bytes);
+    NS_ENSURE_SUCCESS(rv, rv);
+    
+    aCount -= bytes;
+    mTotalBytes += bytes;
+    mDecoder->UpdateBytesDownloaded(mTotalBytes);
+  } while (aCount) ;
+  
+  nsresult rv = mOutput->Flush();
+  NS_ENSURE_SUCCESS(rv, rv);
+  mIntervalEnd = PR_IntervalNow();
+  return NS_OK;
+}
+
+nsIPrincipal*
+nsChannelToPipeListener::GetCurrentPrincipal()
+{
+  return mPrincipal;
+}
+
+NS_IMPL_THREADSAFE_ISUPPORTS2(nsChannelToPipeListener, nsIRequestObserver, nsIStreamListener)
+
new file mode 100644
--- /dev/null
+++ b/content/media/video/src/nsMediaDecoder.cpp
@@ -0,0 +1,275 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: ML 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (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.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla code.
+ *
+ * The Initial Developer of the Original Code is the Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Chris Double <chris.double@double.co.nz>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+#include "prlog.h"
+#include "prmem.h"
+#include "nsIFrame.h"
+#include "nsIDocument.h"
+#include "nsThreadUtils.h"
+#include "nsIDOMHTMLMediaElement.h"
+#include "nsNetUtil.h"
+#include "nsHTMLMediaElement.h"
+#include "nsIObserver.h"
+#include "nsIObserverService.h"
+#include "nsAutoLock.h"
+#include "nsIRenderingContext.h"
+#include "gfxContext.h"
+#include "gfxImageSurface.h"
+#include "nsPresContext.h"
+#include "nsMediaDecoder.h"
+
+#ifdef PR_LOGGING
+// Logging object for decoder
+PRLogModuleInfo* gVideoDecoderLog = nsnull;
+#endif
+
+nsMediaDecoder::nsMediaDecoder() :
+  mElement(0),
+  mRGBWidth(-1),
+  mRGBHeight(-1),
+  mSizeChanged(PR_FALSE),
+  mVideoUpdateLock(nsnull),
+  mFramerate(0.0)
+{
+  MOZ_COUNT_CTOR(nsMediaDecoder);
+}
+
+nsMediaDecoder::~nsMediaDecoder()
+{
+  MOZ_COUNT_DTOR(nsMediaDecoder);
+}
+
+PRBool nsMediaDecoder::Init()
+{
+  mVideoUpdateLock = PR_NewLock();
+
+  return mVideoUpdateLock != nsnull;
+}
+
+void nsMediaDecoder::Shutdown()
+{
+  if (mVideoUpdateLock) {
+    PR_DestroyLock(mVideoUpdateLock);
+    mVideoUpdateLock = nsnull;
+  }
+}
+
+
+nsresult nsMediaDecoder::InitLogger() 
+{
+#ifdef PR_LOGGING
+  gVideoDecoderLog = PR_NewLogModule("nsMediaDecoder");
+#endif
+  return NS_OK;
+}
+
+static void InvalidateCallback(nsITimer* aTimer, void* aClosure)
+{
+  nsMediaDecoder* decoder = static_cast<nsMediaDecoder*>(aClosure);
+  decoder->Invalidate();
+}
+
+nsresult nsMediaDecoder::StartInvalidating(float aFramerate)
+{
+  nsresult rv = NS_OK;
+  if (!mInvalidateTimer && aFramerate > 0.0) {
+    mInvalidateTimer = do_CreateInstance("@mozilla.org/timer;1");
+    rv = mInvalidateTimer->InitWithFuncCallback(InvalidateCallback, 
+                                                this, 
+                                                static_cast<PRInt32>(1000.0/aFramerate), 
+                                                nsITimer::TYPE_REPEATING_PRECISE);
+  }
+
+  return rv;
+}
+
+void nsMediaDecoder::StopInvalidating()
+{
+  if (mInvalidateTimer) {
+    mInvalidateTimer->Cancel();
+    mInvalidateTimer = nsnull;
+  }
+}
+
+void nsMediaDecoder::Invalidate()
+{
+  if (!mElement)
+    return;
+
+  nsIFrame* frame = mElement->GetPrimaryFrame();
+  if (!frame)
+    return;
+  
+  {
+    nsAutoLock lock(mVideoUpdateLock);
+    if (mSizeChanged) {
+      mSizeChanged = PR_FALSE;
+      nsPresContext* presContext = frame->PresContext();      
+      nsIPresShell *presShell = presContext->PresShell();
+      presShell->FrameNeedsReflow(frame, 
+                                  nsIPresShell::eStyleChange,
+                                  NS_FRAME_IS_DIRTY);
+    }
+  }
+  nsRect r(nsPoint(0,0), frame->GetSize());
+  frame->Invalidate(r, PR_FALSE);
+}
+
+static void ProgressCallback(nsITimer* aTimer, void* aClosure)
+{
+  nsMediaDecoder* decoder = static_cast<nsMediaDecoder*>(aClosure);
+  decoder->Progress();
+}
+
+void nsMediaDecoder::Progress()
+{
+  if (!mElement)
+    return;
+
+  mElement->DispatchProgressEvent(NS_LITERAL_STRING("progress"));
+}
+
+nsresult nsMediaDecoder::StartProgress()
+{
+  nsresult rv = NS_OK;
+
+  if (!mProgressTimer) {
+    mProgressTimer = do_CreateInstance("@mozilla.org/timer;1");
+    rv = mProgressTimer->InitWithFuncCallback(ProgressCallback, 
+                                              this, 
+                                              350, // Number of milliseconds defined in spec
+                                              nsITimer::TYPE_REPEATING_PRECISE);
+  }
+  return rv;
+}
+
+nsresult nsMediaDecoder::StopProgress()
+{
+  nsresult rv = NS_OK;
+  if (mProgressTimer) {
+    rv = mProgressTimer->Cancel();
+    mProgressTimer = nsnull;
+  }
+  return rv;
+}
+
+void nsMediaDecoder::MediaSizeChanged()
+{
+  nsAutoLock lock(mVideoUpdateLock);
+  if (mElement)
+    mElement->UpdateMediaSize(nsIntSize(mRGBWidth, mRGBHeight));
+}
+
+void nsMediaDecoder::SetRGBData(PRInt32 aWidth, PRInt32 aHeight, float aFramerate, unsigned char* aRGBBuffer)
+{
+  if (mRGBWidth != aWidth || mRGBHeight != aHeight) {
+    mRGBWidth = aWidth;
+    mRGBHeight = aHeight;
+    mSizeChanged = PR_TRUE;
+    // Delete buffer so we'll reallocate it
+    mRGB = nsnull;
+
+    // Inform the element that the size has changed.
+    nsCOMPtr<nsIRunnable> event = 
+      NS_NEW_RUNNABLE_METHOD(nsMediaDecoder, this, MediaSizeChanged); 
+    
+    NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);    
+  }
+  mFramerate = aFramerate;
+
+  if (!mRGB) 
+    mRGB = new unsigned char[aWidth * aHeight * 4];
+  if (mRGB && aRGBBuffer) {
+    memcpy(mRGB.get(), aRGBBuffer, aWidth*aHeight*4);
+  }
+}
+
+void nsMediaDecoder::Paint(gfxContext* aContext, const gfxRect& aRect)
+{
+  nsAutoLock lock(mVideoUpdateLock);
+
+  if (!mRGB)
+    return;
+
+  /* Create a surface backed by the RGB */
+  nsRefPtr<gfxASurface> surface = 
+    new gfxImageSurface(mRGB,
+                        gfxIntSize(mRGBWidth, mRGBHeight), 
+                        mRGBWidth * 4,
+                        gfxASurface::ImageFormatARGB32);    
+
+  if (!surface)
+    return;
+
+  nsRefPtr<gfxPattern> pat = new gfxPattern(surface);
+  if (!pat)
+    return;
+
+  // Make the source image fill the rectangle completely
+  pat->SetMatrix(gfxMatrix().Scale(mRGBWidth/aRect.Width(), mRGBHeight/aRect.Height()));
+
+  /* Draw RGB surface onto frame */
+  aContext->NewPath();
+  aContext->PixelSnappedRectangleAndSetPattern(aRect, pat);
+  aContext->Fill();
+
+#ifdef DEBUG_FRAME_RATE
+  {
+    // Output frame rate
+    static float last = double(PR_IntervalToMilliseconds(PR_IntervalNow()))/1000.0;
+    float now = double(PR_IntervalToMilliseconds(PR_IntervalNow()))/1000.0;
+    static int count = 0;
+    count++;
+    if (now-last > 10.0) {
+      LOG(PR_LOG_DEBUG, ("Paint Frame Rate = %f (should be %f)\n", (float)count / (float)(now-last), mFramerate));
+      count = 0;
+      last = double(PR_IntervalToMilliseconds(PR_IntervalNow()))/1000.0;
+    }
+  }   
+#endif
+}
+
+void nsMediaDecoder::ElementAvailable(nsHTMLMediaElement* anElement)
+{
+  mElement = anElement;
+}
+
+void nsMediaDecoder::ElementUnavailable()
+{
+  mElement = nsnull;
+}
+
new file mode 100644
--- /dev/null
+++ b/content/media/video/src/nsMediaStream.cpp
@@ -0,0 +1,749 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (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.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla code.
+ *
+ * The Initial Developer of the Original Code is the Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Chris Double <chris.double@double.co.nz>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+#include "nsDebug.h"
+#include "nsMediaStream.h"
+#include "nsMediaDecoder.h"
+#include "nsNetUtil.h"
+#include "nsAutoLock.h"
+#include "nsThreadUtils.h"
+#include "nsIFileChannel.h"
+#include "nsIHttpChannel.h"
+#include "nsISeekableStream.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsIRequestObserver.h"
+#include "nsIStreamListener.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsChannelToPipeListener.h"
+
+class nsDefaultStreamStrategy : public nsStreamStrategy
+{
+public:
+  nsDefaultStreamStrategy(nsMediaDecoder* aDecoder, nsIChannel* aChannel, nsIURI* aURI) :
+    nsStreamStrategy(aDecoder, aChannel, aURI)
+  {
+  }
+  
+  // These methods have the same thread calling requirements 
+  // as those with the same name in nsMediaStream
+  virtual nsresult Open();
+  virtual nsresult Close();
+  virtual nsresult Read(char* aBuffer, PRUint32 aCount, PRUint32* aBytes);
+  virtual nsresult Seek(PRInt32 aWhence, PRInt64 aOffset);
+  virtual PRInt64  Tell();
+  virtual PRUint32 Available();
+  virtual float    DownloadRate();
+  virtual void     Cancel();
+  virtual nsIPrincipal* GetCurrentPrincipal();
+
+private:
+  // Listener attached to channel to constantly download the
+  // media data asynchronously and store it in the pipe. The 
+  // data is obtainable via the mPipeInput member. Use on 
+  // main thread only.
+  nsCOMPtr<nsChannelToPipeListener> mListener;
+
+  // Input stream for the media data currently downloaded 
+  // and stored in the pipe. This can be used from any thread.
+  nsCOMPtr<nsIInputStream>  mPipeInput;
+};
+
+nsresult nsDefaultStreamStrategy::Open()
+{
+  nsresult rv;
+
+  mListener = new nsChannelToPipeListener(mDecoder);
+  NS_ENSURE_TRUE(mListener, NS_ERROR_OUT_OF_MEMORY);
+
+  rv = mListener->Init();
+  NS_ENSURE_SUCCESS(rv, rv);
+  
+  rv = mChannel->AsyncOpen(mListener, nsnull);
+  NS_ENSURE_SUCCESS(rv, rv);
+  
+  rv = mListener->GetInputStream(getter_AddRefs(mPipeInput));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+nsresult nsDefaultStreamStrategy::Close()
+{
+  nsAutoLock lock(mLock);
+  if (mChannel) {
+    mChannel->Cancel(NS_BINDING_ABORTED);
+    mChannel = nsnull;
+
+    mPipeInput->Close();
+    mPipeInput = nsnull;
+
+    mListener = nsnull;
+  }
+
+  return NS_OK;
+}
+
+nsresult nsDefaultStreamStrategy::Read(char* aBuffer, PRUint32 aCount, PRUint32* aBytes)
+{
+  // The read request pulls from the pipe, not the channels input
+  // stream. This allows calling from any thread as the pipe is
+  // threadsafe.
+  nsAutoLock lock(mLock);
+  return mPipeInput ? mPipeInput->Read(aBuffer, aCount, aBytes) : NS_ERROR_FAILURE;
+}
+
+nsresult nsDefaultStreamStrategy::Seek(PRInt32 aWhence, PRInt64 aOffset) 
+{
+  // Default streams cannot be seeked
+  return NS_ERROR_FAILURE;
+}
+
+PRInt64 nsDefaultStreamStrategy::Tell()
+{
+  // Default streams cannot be seeked
+  return 0;
+}
+
+PRUint32 nsDefaultStreamStrategy::Available()
+{
+  // The request pulls from the pipe, not the channels input
+  // stream. This allows calling from any thread as the pipe is
+  // threadsafe.
+  nsAutoLock lock(mLock);
+  if (!mPipeInput)
+    return 0;
+
+  PRUint32 count = 0;
+  mPipeInput->Available(&count);
+  return count;
+}
+
+float nsDefaultStreamStrategy::DownloadRate()
+{
+  nsAutoLock lock(mLock);
+  return mListener ? mListener->BytesPerSecond() : NS_MEDIA_UNKNOWN_RATE;
+}
+
+void nsDefaultStreamStrategy::Cancel()
+{
+  if (mListener)
+    mListener->Cancel();
+}
+
+nsIPrincipal* nsDefaultStreamStrategy::GetCurrentPrincipal()
+{
+  if (!mListener)
+    return nsnull;
+
+  return mListener->GetCurrentPrincipal();
+}
+
+class nsFileStreamStrategy : public nsStreamStrategy
+{
+public:
+  nsFileStreamStrategy(nsMediaDecoder* aDecoder, nsIChannel* aChannel, nsIURI* aURI) :
+    nsStreamStrategy(aDecoder, aChannel, aURI)
+  {
+  }
+  
+  // These methods have the same thread calling requirements 
+  // as those with the same name in nsMediaStream
+  virtual nsresult Open();
+  virtual nsresult Close();
+  virtual nsresult Read(char* aBuffer, PRUint32 aCount, PRUint32* aBytes);
+  virtual nsresult Seek(PRInt32 aWhence, PRInt64 aOffset);
+  virtual PRInt64  Tell();
+  virtual PRUint32 Available();
+  virtual float    DownloadRate();
+  virtual nsIPrincipal* GetCurrentPrincipal();
+
+private:
+  // Seekable stream interface to file. This can be used from any
+  // thread.
+  nsCOMPtr<nsISeekableStream> mSeekable;
+
+  // Input stream for the media data. This can be used from any
+  // thread.
+  nsCOMPtr<nsIInputStream>  mInput;
+
+  // Security Principal
+  nsCOMPtr<nsIPrincipal> mPrincipal;
+};
+
+nsresult nsFileStreamStrategy::Open()
+{
+  nsresult rv;
+
+  rv = mChannel->Open(getter_AddRefs(mInput));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  mSeekable = do_QueryInterface(mInput);
+
+  /* Get our principal */
+  nsCOMPtr<nsIScriptSecurityManager> secMan =
+    do_GetService("@mozilla.org/scriptsecuritymanager;1");
+  if (secMan) {
+    nsresult rv = secMan->GetChannelPrincipal(mChannel,
+                                              getter_AddRefs(mPrincipal));
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
+  }
+
+  // For a file stream the resource is considered loaded since there
+  // is no buffering delays, etc reading.
+  nsCOMPtr<nsIRunnable> event = 
+    NS_NEW_RUNNABLE_METHOD(nsMediaDecoder, mDecoder, ResourceLoaded); 
+  NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
+  
+  return mSeekable ? NS_OK : NS_ERROR_FAILURE;
+}
+
+nsresult nsFileStreamStrategy::Close()
+{
+  nsAutoLock lock(mLock);
+  if (mChannel) {
+    mChannel->Cancel(NS_BINDING_ABORTED);
+    mChannel = nsnull;
+    mInput = nsnull;
+    mSeekable = nsnull;
+  }
+
+  return NS_OK;
+}
+
+nsresult nsFileStreamStrategy::Read(char* aBuffer, PRUint32 aCount, PRUint32* aBytes)
+{
+  nsAutoLock lock(mLock);
+  return mInput ? mInput->Read(aBuffer, aCount, aBytes) : NS_ERROR_FAILURE;
+}
+
+nsresult nsFileStreamStrategy::Seek(PRInt32 aWhence, PRInt64 aOffset) 
+{  
+  nsAutoLock lock(mLock);
+  return mSeekable ? mSeekable->Seek(aWhence, aOffset) : NS_ERROR_FAILURE;
+}
+
+PRInt64 nsFileStreamStrategy::Tell()
+{
+  nsAutoLock lock(mLock);
+  if (!mSeekable)
+    return 0;
+
+  PRInt64 offset = 0;
+  mSeekable->Tell(&offset);
+  return offset;
+}
+
+PRUint32 nsFileStreamStrategy::Available()
+{
+  nsAutoLock lock(mLock);
+  if (!mInput)
+    return 0;
+
+  PRUint32 count = 0;
+  mInput->Available(&count);
+  return count;
+}
+
+float nsFileStreamStrategy::DownloadRate()
+{
+  return NS_MEDIA_UNKNOWN_RATE;
+}
+
+nsIPrincipal* nsFileStreamStrategy::GetCurrentPrincipal()
+{
+  return mPrincipal;
+}
+
+class nsHttpStreamStrategy : public nsStreamStrategy
+{
+public:
+  nsHttpStreamStrategy(nsMediaDecoder* aDecoder, nsIChannel* aChannel, nsIURI* aURI) :
+    nsStreamStrategy(aDecoder, aChannel, aURI),
+    mPosition(0),
+    mEOFPosition(-1),
+    mCancelled(PR_FALSE)
+  {
+  }
+  
+  // These methods have the same thread calling requirements 
+  // as those with the same name in nsMediaStream
+  virtual nsresult Open();
+  virtual nsresult Close();
+  virtual nsresult Read(char* aBuffer, PRUint32 aCount, PRUint32* aBytes);
+  virtual nsresult Seek(PRInt32 aWhence, PRInt64 aOffset);
+  virtual PRInt64  Tell();
+  virtual PRUint32 Available();
+  virtual float    DownloadRate();
+  virtual void     Cancel();
+  virtual nsIPrincipal* GetCurrentPrincipal();
+
+  // Return PR_TRUE if the stream has been cancelled.
+  PRBool IsCancelled() const;
+
+  // This must be called on the main thread only, and at a
+  // time when the strategy is not reading from the current
+  // channel/stream. It's primary purpose is to be called from
+  // a Seek to reset to the new byte range request http channel.
+  void Reset(nsIChannel* aChannel, 
+             nsChannelToPipeListener* aListener, 
+             nsIInputStream* aStream);
+
+private:
+  // Listener attached to channel to constantly download the
+  // media data asynchronously and store it in the pipe. The 
+  // data is obtainable via the mPipeInput member. Use on 
+  // main thread only.
+  nsCOMPtr<nsChannelToPipeListener> mListener;
+
+  // Input stream for the media data currently downloaded 
+  // and stored in the pipe. This can be used from any thread.
+  nsCOMPtr<nsIInputStream>  mPipeInput;
+
+  // Current seek position. Need to compute this manually due to
+  // seeking with byte range requests meaning the position in the pipe
+  // is not valid. This is initially set on the main thread during the
+  // Open call. After that it is read and written by a single thread
+  // only (the thread that calls the read/seek operations).
+  PRInt64 mPosition;
+
+  // End of file seek position. Set when liboggz requests a 
+  // seek to end of file, otherwise -1. This is written and read
+  // from a single thread only (the thread that calls the read/seek
+  // operations).
+  PRInt64 mEOFPosition;
+
+  // PR_TRUE if the media stream requested this strategy is cancelled.
+  // This is read and written on the main thread only.
+  PRPackedBool mCancelled;
+};
+
+void nsHttpStreamStrategy::Reset(nsIChannel* aChannel, 
+                                 nsChannelToPipeListener* aListener, 
+                                 nsIInputStream* aStream)
+{
+  nsAutoLock lock(mLock);
+  mChannel = aChannel;
+  mListener = aListener;
+  mPipeInput = aStream;
+}
+
+nsresult nsHttpStreamStrategy::Open()
+{
+  nsresult rv;
+
+  mListener = new nsChannelToPipeListener(mDecoder);
+  NS_ENSURE_TRUE(mListener, NS_ERROR_OUT_OF_MEMORY);
+
+  rv = mListener->Init();
+  NS_ENSURE_SUCCESS(rv, rv);
+  
+  rv = mChannel->AsyncOpen(mListener, nsnull);
+  NS_ENSURE_SUCCESS(rv, rv);
+  
+  rv = mListener->GetInputStream(getter_AddRefs(mPipeInput));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  mPosition = 0;
+
+  return NS_OK;
+}
+
+nsresult nsHttpStreamStrategy::Close()
+{
+  nsAutoLock lock(mLock);
+  if (mChannel) {
+    mChannel->Cancel(NS_BINDING_ABORTED);
+    mChannel = nsnull;
+
+    mPipeInput->Close();
+    mPipeInput = nsnull;
+
+    mListener = nsnull;
+  }
+
+  return NS_OK;
+}
+
+nsresult nsHttpStreamStrategy::Read(char* aBuffer, PRUint32 aCount, PRUint32* aBytes)
+{
+  // The read request pulls from the pipe, not the channels input
+  // stream. This allows calling from any thread as the pipe is
+  // threadsafe.
+  nsAutoLock lock(mLock);
+  if (!mPipeInput)
+    return NS_ERROR_FAILURE;
+
+  nsresult rv = mPipeInput->Read(aBuffer, aCount, aBytes);
+  mPosition += *aBytes;
+
+  return rv;
+}
+
+class nsByteRangeEvent : public nsRunnable 
+{
+public:
+  nsByteRangeEvent(nsHttpStreamStrategy* aStrategy, 
+                   nsMediaDecoder* aDecoder, 
+                   nsIURI* aURI, 
+                   PRInt64 aOffset) :
+    mStrategy(aStrategy),
+    mDecoder(aDecoder),
+    mURI(aURI),
+    mOffset(aOffset),
+    mResult(NS_OK)
+  {
+    MOZ_COUNT_CTOR(nsByteRangeEvent);
+  }
+
+  ~nsByteRangeEvent()
+  {
+    MOZ_COUNT_DTOR(nsByteRangeEvent);
+  }
+
+  nsresult GetResult()
+  {
+    return mResult;
+  }
+
+  NS_IMETHOD Run() {
+    // This event runs in the main thread. The same
+    // thread as that which can block waiting for the
+    // decode event to complete when the stream
+    // is cancelled. Check for to see if we are cancelled
+    // in case this event is processed after the cancel flag
+    // which would otherwise cause the listener to be recreated.
+    if (mStrategy->IsCancelled()) {
+      mResult = NS_ERROR_FAILURE;
+      return NS_OK;
+    }
+
+    mStrategy->Close();
+    mResult = NS_NewChannel(getter_AddRefs(mChannel),
+                            mURI,
+                            nsnull,
+                            nsnull,
+                            nsnull,
+                            nsIRequest::LOAD_NORMAL);
+    NS_ENSURE_SUCCESS(mResult, mResult);
+
+    nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(mChannel);
+    if (hc) {
+      nsCAutoString rangeString("bytes=");
+      rangeString.AppendInt(mOffset);
+      rangeString.Append("-");
+      hc->SetRequestHeader(NS_LITERAL_CSTRING("Range"), rangeString, PR_FALSE);
+    }
+
+    mListener = new nsChannelToPipeListener(mDecoder);
+    NS_ENSURE_TRUE(mListener, NS_ERROR_OUT_OF_MEMORY);
+
+    mResult = mListener->Init();
+    NS_ENSURE_SUCCESS(mResult, mResult);
+
+    mResult = mChannel->AsyncOpen(mListener, nsnull);
+    NS_ENSURE_SUCCESS(mResult, mResult);
+
+    mResult = mListener->GetInputStream(getter_AddRefs(mStream));
+    NS_ENSURE_SUCCESS(mResult, mResult);
+
+    mStrategy->Reset(mChannel, mListener, mStream);
+    return NS_OK;
+  }
+
+private:
+  nsHttpStreamStrategy* mStrategy;
+  nsMediaDecoder* mDecoder;
+  nsIURI* mURI;
+  nsCOMPtr<nsIChannel> mChannel;
+  nsCOMPtr<nsChannelToPipeListener> mListener;
+  nsCOMPtr<nsIInputStream> mStream;
+  PRInt64 mOffset;
+  nsresult mResult;
+};
+
+nsresult nsHttpStreamStrategy::Seek(PRInt32 aWhence, PRInt64 aOffset) 
+{
+  {
+    nsAutoLock lock(mLock);
+    if (!mChannel || !mPipeInput) 
+      return NS_ERROR_FAILURE;
+
+    // When seeking liboggz will first seek to the end of the file to
+    // obtain the length of the file. It immediately does a 'tell' to
+    // get the position and reseeks somewhere else. This traps the seek
+    // to end of file and just returns the content length.
+    if(aWhence == nsISeekableStream::NS_SEEK_END && aOffset == 0) {
+      PRInt32 length;
+      mChannel->GetContentLength(&length);
+      if (length == -1)
+        return NS_ERROR_FAILURE;
+      
+      mEOFPosition = length;
+      return NS_OK;
+    }
+
+    // Handle cases of aWhence not being NS_SEEK_SET but converting to
+    // NS_SEEK_SET
+    switch (aWhence) {
+    case nsISeekableStream::NS_SEEK_END: {
+      PRInt32 length;
+      mChannel->GetContentLength(&length);
+      if (length == -1)
+        return NS_ERROR_FAILURE;
+      
+      aOffset -= length; 
+      aWhence = nsISeekableStream::NS_SEEK_SET;
+      break;
+    }
+    case nsISeekableStream::NS_SEEK_CUR: {
+      aOffset += mPosition;
+      aWhence = nsISeekableStream::NS_SEEK_SET;
+      break;
+    }
+    default:
+      // Do nothing, we are NS_SEEK_SET 
+      break;
+    };
+    
+    // If we are already at the correct position, do nothing
+    if (aOffset == mPosition) 
+      return NS_OK;
+
+    // If we are seeking to a byterange that we already have buffered in
+    // the listener then move to that and avoid the need to send a byte
+    // range request.
+    PRInt32 bytesAhead = aOffset - mPosition;
+    PRUint32 available = 0;
+    nsresult rv = mPipeInput->Available(&available);
+    if (NS_SUCCEEDED(rv) && bytesAhead > 0 && available >= PRUint32(bytesAhead)) {
+      nsAutoArrayPtr<char> data(new char[bytesAhead]);
+      if (!data)
+        return NS_ERROR_OUT_OF_MEMORY;
+      
+      PRUint32 bytes = 0;
+      nsresult rv = mPipeInput->Read(data.get(), bytesAhead, &bytes);
+      
+      mPosition += bytesAhead;
+      return rv;
+    }
+  }
+
+  // Don't acquire mLock in this scope as we do a synchronous call to the main thread
+  // which would deadlock if that thread is calling Close().
+  nsCOMPtr<nsByteRangeEvent> event = new nsByteRangeEvent(this, mDecoder, mURI, aOffset);
+  NS_DispatchToMainThread(event, NS_DISPATCH_SYNC);
+
+  mPosition = aOffset;
+  return event->GetResult();
+}
+
+PRInt64 nsHttpStreamStrategy::Tell()
+{
+  // Handle the case of a seek to EOF by liboggz
+  // (See Seek for details)
+  if (mEOFPosition != -1) {
+    PRInt64 result = mEOFPosition;
+    mEOFPosition = -1;
+    return result;
+  }
+
+  return mPosition;
+}
+
+PRUint32 nsHttpStreamStrategy::Available()
+{
+  // The request pulls from the pipe, not the channels input
+  // stream. This allows calling from any thread as the pipe is
+  // threadsafe.
+  nsAutoLock lock(mLock);
+  if (!mPipeInput)
+    return 0;
+
+  PRUint32 count = 0;
+  mPipeInput->Available(&count);
+  return count;
+}
+
+float nsHttpStreamStrategy::DownloadRate()
+{
+  nsAutoLock lock(mLock);
+  if (!mListener)
+    return NS_MEDIA_UNKNOWN_RATE;
+  return mListener->BytesPerSecond();
+}
+
+void nsHttpStreamStrategy::Cancel()
+{
+  mCancelled = PR_TRUE;
+  if (mListener)
+    mListener->Cancel();
+}
+
+PRBool nsHttpStreamStrategy::IsCancelled() const
+{
+  return mCancelled;
+}
+
+nsIPrincipal* nsHttpStreamStrategy::GetCurrentPrincipal()
+{
+  if (!mListener)
+    return nsnull;
+
+  return mListener->GetCurrentPrincipal();
+}
+
+nsMediaStream::nsMediaStream()  :
+  mPlaybackRateCount(0)
+{
+  NS_ASSERTION(NS_IsMainThread(), 
+	       "nsMediaStream created on non-main thread");
+  MOZ_COUNT_CTOR(nsMediaStream);
+}
+
+nsMediaStream::~nsMediaStream()
+{
+  MOZ_COUNT_DTOR(nsMediaStream);
+}
+
+nsresult nsMediaStream::Open(nsMediaDecoder* aDecoder, nsIURI* aURI)
+{
+  NS_ASSERTION(NS_IsMainThread(), 
+	       "nsMediaStream::Open called on non-main thread");
+
+  nsresult rv;
+
+  nsCOMPtr<nsIChannel> channel;
+  rv = NS_NewChannel(getter_AddRefs(channel), 
+                     aURI, 
+                     nsnull,
+                     nsnull,
+                     nsnull,
+                     nsIRequest::LOAD_NORMAL);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsIFileChannel> fc = do_QueryInterface(channel);
+  nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(channel);
+  if (hc) 
+    mStreamStrategy = new nsHttpStreamStrategy(aDecoder, channel, aURI);
+  else if (fc) 
+    mStreamStrategy = new nsFileStreamStrategy(aDecoder, channel, aURI);
+  else
+    mStreamStrategy = new nsDefaultStreamStrategy(aDecoder, channel, aURI);
+
+  mPlaybackRateCount = 0;
+  mPlaybackRateStart = PR_IntervalNow();
+
+  return mStreamStrategy->Open();
+}
+
+nsresult nsMediaStream::Close()
+{
+  NS_ASSERTION(NS_IsMainThread(), 
+	       "nsMediaStream::Close called on non-main thread");
+
+  return mStreamStrategy->Close();
+}
+
+nsresult nsMediaStream::Read(char* aBuffer, PRUint32 aCount, PRUint32* aBytes)
+{
+  nsresult rv = mStreamStrategy->Read(aBuffer, aCount, aBytes);
+  mPlaybackRateCount += *aBytes;    
+  return rv;
+}
+
+nsresult nsMediaStream::Seek(PRInt32 aWhence, PRInt64 aOffset) 
+{
+  return mStreamStrategy->Seek(aWhence, aOffset);
+}
+
+PRInt64 nsMediaStream::Tell()
+{
+  return mStreamStrategy->Tell();
+}
+
+PRUint32 nsMediaStream::Available()
+{
+  return mStreamStrategy->Available();
+}
+
+float nsMediaStream::DownloadRate()
+{
+  return mStreamStrategy->DownloadRate();
+}
+
+float nsMediaStream::PlaybackRate()
+{
+  PRIntervalTime now = PR_IntervalNow();
+  PRUint32 interval = PR_IntervalToMilliseconds(now - mPlaybackRateStart);
+  return static_cast<float>(mPlaybackRateCount) * 1000 / interval;
+}
+
+void nsMediaStream::Cancel()
+{
+  NS_ASSERTION(NS_IsMainThread(), 
+	       "nsMediaStream::Cancel called on non-main thread");
+
+  // In the Http strategy case the cancel will cause the http
+  // channel's listener to close the pipe, forcing an i/o error on any
+  // blocked read. This will allow the decode thread to complete the
+  // event.
+  // 
+  // In the case of a seek in progress, the byte range request creates
+  // a new listener. This is done on the main thread via seek
+  // synchronously dispatching an event. This avoids the issue of us
+  // closing the listener but an outstanding byte range request
+  // creating a new one. They run on the same thread so no explicit
+  // synchronisation is required. The byte range request checks for
+  // the cancel flag and does not create a new channel or listener if
+  // we are cancelling.
+  //
+  // The default strategy does not do any seeking - the only issue is
+  // a blocked read which it handles by causing the listener to close
+  // the pipe, as per the http case.
+  //
+  // The file strategy doesn't block for any great length of time so
+  // is fine for a no-op cancel.
+  mStreamStrategy->Cancel();
+}
+
+nsIPrincipal* nsMediaStream::GetCurrentPrincipal()
+{
+  return mStreamStrategy->GetCurrentPrincipal();
+}
--- a/content/media/video/src/nsOggDecoder.cpp
+++ b/content/media/video/src/nsOggDecoder.cpp
@@ -68,568 +68,707 @@
 // The number of frames to read before audio callback is called.
 // This value is the one used by the oggplay examples.
 #define OGGPLAY_FRAMES_PER_CALLBACK 2048
 
 // Offset into Ogg buffer containing audio information. This value
 // is the one used by the oggplay examples.
 #define OGGPLAY_AUDIO_OFFSET 250L
 
-// Maximum mumber of milliseconds to pause while buffering video data
-// on a slow connection. The DATA_WAIT is used to compute the maximum
-// amount of data to wait for based on the current bytes/second
-// download rate. The TIME_WAIT is the total maximum time to wait in
-// case the bytes/second rate slows down.
-#define MAX_BUFFERING_DATA_WAIT_MS 15000
-#define MAX_BUFFERING_TIME_WAIT_MS 30000
+// Time in seconds to pause while buffering video data
+// on a slow connection.
+#define BUFFERING_WAIT 15
+
+// Download rate can take some time to stabalise. For the buffering
+// computation there is a minimum rate to ensure a reasonable amount
+// gets buffered.
+#define BUFFERING_MIN_RATE 50000
+#define BUFFERING_RATE(x) ((x)< BUFFERING_MIN_RATE ? BUFFERING_MIN_RATE : (x))
 
-// An Event that uses an nsOggDecoder object. It provides a
-// way to inform the event that the decoder object is no longer
-// valid, so queued events can safely ignore it.
-class nsDecoderEvent : public nsRunnable {
-public:
-  nsDecoderEvent(nsOggDecoder* decoder) : 
-    mLock(nsnull), 
-    mDecoder(decoder)
-  {
-  }
+// The number of seconds of buffer data before buffering happens
+// based on current playback rate.
+#define BUFFERING_SECONDS_WATERMARK 1
+
+/* Handle the state machine for displaying video and playing audio.
 
-  PRBool Init()
-  {
-    mLock = PR_NewLock();
-    return mLock != nsnull;
-  }
+   All internal state is synchronised via the decoder
+   monitor. NotifyAll on the monitor is called when state is
+   changed. The following changes to state cause a notify:
+
+     A frame has been displayed
 
-  virtual ~nsDecoderEvent()
-  {
-    if (mLock) {
-      PR_DestroyLock(mLock);
-      mLock = nsnull;
-    }
-  }
+   See nsOggDecoder.h for more details.
+*/
+class nsOggDisplayStateMachine : public nsRunnable
+{
+public:
+  nsOggDisplayStateMachine(nsOggDecoder* aDecoder);
+  ~nsOggDisplayStateMachine();
 
-  void Revoke()
-  {
-    nsAutoLock lock(mLock);
-    mDecoder = nsnull;
-  }
+  // Called from the main thread to get the current frame time. The decoder
+  // monitor must be obtained before calling this.
+  float GetCurrentTime();
 
-  void Lock() 
-  {
-    PR_Lock(mLock);
-  }
+  // Get and set the audio volume. The decoder monitor must be
+  // obtained before calling this.
+  float GetVolume();
+  void SetVolume(float aVolume);
 
-  void Unlock() 
-  {
-    PR_Unlock(mLock);
-  }
+  NS_IMETHOD Run();
 
-  NS_IMETHOD Run() {
-    nsAutoLock lock(mLock);
-    return RunWithLock();
-  }
+  // Update the current frame time. Used during a seek when the initial frame is
+  // displayed and a new time for that frame is computed. Must be called with a
+  // lock on the decoder monitor.
+  void UpdateFrameTime(float aTime);
 
 protected:
-  virtual nsresult RunWithLock() = 0;
+  // Initializes and opens the audio stream. Called from the display
+  // thread only. Must be called with the decode monitor held.
+  void OpenAudioStream();
+
+  // Closes and releases resources used by the audio stream. Called
+  // from the display thread only. Must be called with the decode
+  // monitor held.
+  void CloseAudioStream();
+
+  // Start playback of audio, either by opening or resuming the audio
+  // stream. Must be called with the decode monitor held.
+  void StartAudio();
+
+  // Stop playback of audio, either by closing or pausing the audio
+  // stream. Must be called with the decode monitor held.
+  void StopAudio();
 
-  PRLock* mLock;
+private:
+  // The decoder object that created this state machine. The decoder
+  // always outlives us since it controls our lifetime.
   nsOggDecoder* mDecoder;
+
+  // The audio stream resource. Used on the display thread and the
+  // main thread (Via the Get/SetVolume calls). Synchronisation via
+  // mDecoder monitor.
+  nsAutoPtr<nsAudioStream> mAudioStream;
+
+  // The time of the current frame in seconds. This is referenced from
+  // 0.0 which is the initial start of the stream. Set by the display
+  // thread, and read-only from the main thread to get the current
+  // time value. Synchronised via decoder monitor.
+  float mCurrentFrameTime;
+
+  // The last time interval that a video frame was displayed. Used for
+  // computing the sleep period between frames for a/v sync.
+  // Read/Write from the display thread only.
+  PRIntervalTime mLastFrameTime;
+
+  // Volume of playback. 0.0 = muted. 1.0 = full volume. Read/Written
+  // from the display and main threads. Synchronised via decoder
+  // monitor.
+  float mVolume;
 };
 
-// This handles the Ogg decoding loop. It blocks waiting for
-// ogg data, decodes that data, and then goes back to blocking.
-// It is synchronised with the nsVideoPresentationEvent internally
-// by OggPlay so that it doesn't go to far ahead or behind the
-// display of the video frame data.
-class nsVideoDecodeEvent : public nsDecoderEvent
-{
-public:
-  nsVideoDecodeEvent(nsOggDecoder* decoder) :
-    nsDecoderEvent(decoder)
-  {
-  }
+/* 
+  All reading (including seeks) from the nsMediaStream are done on the
+  decoding thread. The decoder thread is informed before closing that
+  the stream is about to close via the Shutdown
+  event. oggplay_prepare_for_close is called before sending the
+  shutdown event to tell liboggplay to shutdown.
+
+  This call results in oggplay internally not calling any
+  read/write/seek/tell methods, and returns a value that results in
+  stopping the decoder thread.
+
+  oggplay_close is called in the destructor which results in the media
+  stream being closed. This is how the nsMediaStream contract that no
+  read/seeking must occur during or after Close is called is enforced.
 
-protected:
-  nsresult RunWithLock() 
-  {
-    if (mDecoder && mDecoder->StepDecoding()) {
-      NS_GetCurrentThread()->Dispatch(this, NS_DISPATCH_NORMAL);
-    }
-    else {
-      LOG(PR_LOG_DEBUG, ("Decoding thread completed"));
-    }
-    return NS_OK;
-  }
+  This object keeps pointers to the nsOggDecoder and nsChannelReader
+  objects.  Since the lifetime of nsOggDecodeStateMachine is
+  controlled by nsOggDecoder it will never have a stale reference to
+  these objects. The reader is destroyed by the call to oggplay_close
+  which is done in the destructor so again this will never be a stale
+  reference.
 
-private:
-  // PR_TRUE if we are actively decoding
-  PRPackedBool mDecoding;
-};
+  All internal state is synchronised via the decoder
+  monitor. NotifyAll on the monitor is called when the state of the
+  state machine is changed. The following changes to state cause a
+  notify:
 
-class nsVideoPresentationEvent : public nsDecoderEvent
+    mState and data related to that state changed (mSeekTime, etc)
+    Ogg Metadata Loaded
+    First Frame Loaded  
+    Frame decoded    
+    
+  See nsOggDecoder.h for more details.
+*/
+class nsOggDecodeStateMachine : public nsRunnable
 {
 public:
-  nsVideoPresentationEvent(nsOggDecoder* decoder) :
-    nsDecoderEvent(decoder)
+  // Enumeration for the valid states
+  enum State {
+    DECODER_STATE_DECODING_METADATA,
+    DECODER_STATE_DECODING_FIRSTFRAME,
+    DECODER_STATE_DECODING,
+    DECODER_STATE_SEEKING,
+    DECODER_STATE_BUFFERING,
+    DECODER_STATE_COMPLETED,
+    DECODER_STATE_SHUTDOWN
+  };
+
+  nsOggDecodeStateMachine(nsOggDecoder* aDecoder, nsChannelReader* aReader);
+  ~nsOggDecodeStateMachine();
+
+  // Cause state transitions. These methods obtain the decoder monitor
+  // to synchronise the change of state, and to notify other threads
+  // that the state has changed.
+  void Shutdown();
+  void Decode();
+  void Seek(float aTime);
+
+  NS_IMETHOD Run();
+
+  // Provide access to the current state. Must be called with a lock
+  // on the decoder's monitor.
+  State GetState()
   {
+    return mState;
+  }
+
+  // Provide access to the ogg metadata. Must be called with a lock on
+  // the decoder's monitor. The metadata is sent when in the state
+  // DECODING_METADATA and is read-only after that point. For this
+  // reason these methods should only be called if the current state
+  // is greater than DECODING_METADATA.
+  float GetFramerate()
+  {
+    NS_ASSERTION(mState > DECODER_STATE_DECODING_METADATA, "GetFramerate() called during invalid state");
+    //  NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "GetFramerate() called without acquiring decoder monitor");
+    return mFramerate;
+  }
+
+  PRBool HasAudio()
+  {
+    NS_ASSERTION(mState > DECODER_STATE_DECODING_METADATA, "HasAudio() called during invalid state");
+    //  NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "HasAudio() called without acquiring decoder monitor");
+    return mAudioTrack != -1;
+  }
+
+  PRInt32 GetAudioRate()
+  {
+    NS_ASSERTION(mState > DECODER_STATE_DECODING_METADATA, "GetAudioRate() called during invalid state");
+    //  NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "GetAudioRate() called without acquiring decoder monitor");
+    return mAudioRate;
+  }
+
+  PRInt32 GetAudioChannels()
+  {
+    NS_ASSERTION(mState > DECODER_STATE_DECODING_METADATA, "GetAudioChannels() called during invalid state");
+    //  NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "GetAudioChannels() called without acquiring decoder monitor");
+    return mAudioChannels;
+  }
+  
+  // Return PR_TRUE if we have completed decoding or are shutdown. Must
+  // be called with a lock on the decoder's monitor.
+  PRBool IsCompleted()
+  {
+    //  NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "IsCompleted() called without acquiring decoder monitor");
+    return 
+      mState == DECODER_STATE_COMPLETED ||
+      mState == DECODER_STATE_SHUTDOWN;
   }
 
-  // Stop the invalidation timer. When we aren't decoding
-  // video frames we stop the timer since it takes a fair
-  // amount of CPU on some platforms.
-  void StopInvalidating()
-  {
-    if (mDecoder) {
-      // Stop the invalidation timer
-      nsCOMPtr<nsIRunnable> event = 
-        NS_NEW_RUNNABLE_METHOD(nsOggDecoder, mDecoder, StopInvalidating); 
-      
-      if (event) {
-        NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
-      }
-    }
-  }
-  
-  nsresult RunWithLock() {
-    if (mDecoder && !mDecoder->IsPaused() && mDecoder->StepDisplay()) {
-      NS_GetCurrentThread()->Dispatch(this, NS_DISPATCH_NORMAL);
-    }
-    else {
-      LOG(PR_LOG_DEBUG, ("Presentation thread completed"));
-      StopInvalidating();
-    }
-    return NS_OK;
-  }
+  // Decode one frame of data, returning the OggPlay error code. Must
+  // be called only when the current state > DECODING_METADATA. The decode 
+  // monitor does not need to be locked during this call since liboggplay
+  // internally handles locking.
+  // Any return value apart from those below is mean decoding cannot continue.
+  // E_OGGPLAY_CONTINUE       = One frame decoded and put in buffer list
+  // E_OGGPLAY_USER_INTERRUPT = One frame decoded, buffer list is now full
+  // E_OGGPLAY_TIMEOUT        = No frames decoded, timed out
+  OggPlayErrorCode DecodeFrame();
+
+  // Returns the next decoded frame of data. Resources owned by the
+  // frame must be released via a call to ReleaseFrame. This function
+  // must be called only when the current state >
+  // DECODING_METADATA. The decode monitor lock does not need to be
+  // locked during this call since liboggplay internally handles
+  // locking.
+  OggPlayCallbackInfo** NextFrame();
+
+  // Frees the resources owned by a frame obtained from
+  // NextFrame(). This function must be called only when the current
+  // state > DECODING_METADATA. The decode monitor must be acquired
+  // before calling.
+  void ReleaseFrame(OggPlayCallbackInfo** aFrame);
+
+  // These methods handle the display of the decoded OggPlay data.
+  // They are usually called by the display thread, but can be called
+  // from any thread as long as the decode monitor is locked.  They
+  // must be called only when the current state > DECODING_METADATA.
+  // Returns the timestamp, in seconds, of the frame that was
+  // processed.
+  float DisplayFrame(OggPlayCallbackInfo** aFrame, nsAudioStream* aAudioStream);
+
+  // Decode and display the initial frame after the oggplay buffers
+  // have been cleared (during a seek for example).The decode monitor
+  // must be obtained before calling.
+  float DisplayInitialFrame();
+
+protected:
+  // Handle the display of tracks within the frame (audio or
+  // video). The decoder monitor must be acquired in the scope of
+  // calls to these functions. They must be called only when the
+  // current state > DECODING_METADATA. DisplayTrack returns the
+  // timestamp, in seconds, of the track that was processed.
+  float DisplayTrack(int aTrackNumber, OggPlayCallbackInfo* aTrack, nsAudioStream* aAudioStream);
+  void HandleVideoData(int aTrackNum, OggPlayVideoData* aVideoData);
+  void HandleAudioData(nsAudioStream* aAudioStream, OggPlayAudioData* aAudioData, int aSize);
+  void CopyVideoFrame(int aTrackNum, OggPlayVideoData* aVideoData, float aFramerate);
+
+  // These methods can only be called on the decoding thread.
+  void LoadOggHeaders();
+  void LoadFirstFrame();
+
+private:
+  // The decoder object that created this state machine. The decoder
+  // always outlives us since it controls our lifetime.
+  nsOggDecoder* mDecoder;
+
+  // The OggPlay handle. Synchronisation of calls to oggplay functions
+  // are handled by liboggplay. We control the lifetime of this
+  // object, destroying it in our destructor.
+  OggPlay* mPlayer;
+
+  // Channel Reader. Originally created by the mDecoder object, it is
+  // destroyed when we close the mPlayer handle in the
+  // destructor. Used to obtain download and playback rate information
+  // for buffering.  Synchronisation for those methods are handled by
+  // nsMediaStream.
+  nsChannelReader* mReader;
+
+  // Video data. These are initially set when the metadata is loaded.
+  // After that they are read only. The decoder monitor lock must be
+  // obtained before reading.
+  PRInt32 mVideoTrack;
+  float   mFramerate;
+
+  // Audio data. These are initially set when the metadata is loaded.
+  // After that they are read only. The decoder monitor lock must be
+  // obtained before reading.
+  PRInt32 mAudioRate;
+  PRInt32 mAudioChannels;
+  PRInt32 mAudioTrack;
+
+  // The decoder monitor must be obtained before modifying this state.
+  // NotifyAll on the monitor must be called when state is changed so
+  // other threads can react to changes.
+  State mState;
+
+  // Time that buffering started. Used for buffering timeout and only
+  // accessed in the decoder thread.
+  PRIntervalTime mBufferingStart;
+
+  // Number of bytes to buffer when buffering. Only accessed in the
+  // decoder thread.
+  PRUint32 mBufferingBytes;
+
+  // Position to seek to when the seek state transition occurs. The
+  // decoder monitor lock must be obtained before reading or writing
+  // this value.
+  float mSeekTime;
+
+  // PR_TRUE if the oggplay buffer is full and we'll block on the next
+  // ogg_step_decoding call. The decode monitor lock must be obtained
+  // before reading/writing this value.
+  PRPackedBool mBufferFull;
 };
 
-NS_IMPL_THREADSAFE_ISUPPORTS1(nsOggDecoder, nsIObserver)
-
-void nsOggDecoder::Pause() 
-{
-  if (!mPresentationThread)
-    return;
-
-  mPaused = PR_TRUE;
-  nsCOMPtr<nsIRunnable> event = 
-    NS_NEW_RUNNABLE_METHOD(nsOggDecoder, this, DoPause); 
-  if (event)
-    mPresentationThread->Dispatch(event, NS_DISPATCH_NORMAL);
-}
-
-void nsOggDecoder::DoPause() 
-{
-  mPaused = PR_TRUE;
-  if (mAudioStream) {
-    mAudioStream->Pause();
-  }
-  mSystemSyncSeconds = double(PR_IntervalToMilliseconds(PR_IntervalNow()))/1000.0;
-}
-
-float nsOggDecoder::GetVolume()
-{
-  float volume = 0.0;
-  if (mAudioStream) {
-    mAudioStream->GetVolume(&volume);
-  }
-  else {
-    volume = mInitialVolume;
-  }
-
-  return volume;
-}
-
-void nsOggDecoder::SetVolume(float volume)
-{
-  if (mAudioStream) {
-    mAudioStream->SetVolume(volume);
-  }
-  else {
-    mInitialVolume = volume;
-  }
-}
-
-float nsOggDecoder::GetDuration()
-{
-  // Currently not implemented. Video Spec says to return
-  // NaN if unknown.
-  // TODO: return NaN
-  return 0.0;
-}
-
-nsOggDecoder::nsOggDecoder() :
-  nsVideoDecoder(),
-  mBytesDownloaded(0),
-  mVideoNextFrameTime(0.0),
-  mLoadInProgress(PR_FALSE),
-  mPlayAfterLoad(PR_FALSE),
-  mNotifyOnShutdown(PR_FALSE),
-  mVideoCurrentFrameTime(0.0),
-  mInitialVolume(1.0),
+nsOggDecodeStateMachine::nsOggDecodeStateMachine(nsOggDecoder* aDecoder, nsChannelReader* aReader) :
+  mDecoder(aDecoder),
+  mPlayer(0),
+  mReader(aReader),
+  mVideoTrack(-1),
+  mFramerate(0.0),
   mAudioRate(0),
   mAudioChannels(0),
   mAudioTrack(-1),
-  mVideoTrack(-1),
-  mPlayer(0),
-  mReader(0),
-  mPaused(PR_TRUE),
-  mFirstFrameLoaded(PR_FALSE),
-  mFirstFrameCondVar(nsnull),
-  mFirstFrameLock(nsnull),
-  mSystemSyncSeconds(0.0),
-  mResourceLoaded(PR_FALSE),
-  mMetadataLoaded(PR_FALSE)
+  mState(DECODER_STATE_DECODING_METADATA),
+  mBufferingStart(0),
+  mBufferingBytes(0),
+  mSeekTime(0.0),
+  mBufferFull(PR_FALSE)
 {
 }
 
-PRBool nsOggDecoder::Init() 
+nsOggDecodeStateMachine::~nsOggDecodeStateMachine()
 {
-  mFirstFrameLock = PR_NewLock();  
-  mFirstFrameCondVar = mFirstFrameLock ? PR_NewCondVar(mFirstFrameLock) : nsnull ;
+  oggplay_close(mPlayer);
+}
+
 
-  mDecodeEvent = new nsVideoDecodeEvent(this);
-  mPresentationEvent = new nsVideoPresentationEvent(this);
+OggPlayErrorCode nsOggDecodeStateMachine::DecodeFrame()
+{
+  NS_ASSERTION(mState > DECODER_STATE_DECODING_METADATA, "DecodeFrame() called during invalid state");
+  return oggplay_step_decoding(mPlayer);
+}
 
-  return mFirstFrameLock &&
-    mFirstFrameCondVar &&
-    mDecodeEvent && mDecodeEvent->Init() &&
-    mPresentationEvent && mPresentationEvent->Init() &&
-    nsVideoDecoder::Init();
+OggPlayCallbackInfo** nsOggDecodeStateMachine::NextFrame()
+{
+  NS_ASSERTION(mState > DECODER_STATE_DECODING_METADATA, "NextFrame() called during invalid state");
+  return oggplay_buffer_retrieve_next(mPlayer);
 }
 
-void nsOggDecoder::Shutdown() 
+void nsOggDecodeStateMachine::ReleaseFrame(OggPlayCallbackInfo** aFrame)
 {
-  if (mDecodeEvent) {
-    // Must prepare oggplay for close to stop the decode event from
-    // waiting on oggplay's semaphore. Without this, revoke deadlocks.
-    if (mPlayer) {
-      oggplay_prepare_for_close(mPlayer);
-    }
-
-    mDecodeEvent->Revoke();
-    mDecodeEvent = nsnull;
-  }
-  if (mPresentationEvent) {
-    mPresentationEvent->Revoke();
-    mPresentationEvent = nsnull;
-  }
-
-  Stop();
-  nsVideoDecoder::Shutdown();
+  //  NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "ReleaseFrame() called without acquiring decoder monitor");
+  NS_ASSERTION(mState > DECODER_STATE_DECODING_METADATA, "ReleaseFrame() called during invalid state");
+  oggplay_buffer_release(mPlayer, aFrame);
+  mBufferFull = PR_FALSE;
 }
 
-nsOggDecoder::~nsOggDecoder()
+float nsOggDecodeStateMachine::DisplayFrame(OggPlayCallbackInfo** aFrame, nsAudioStream* aAudioStream)
 {
-  Shutdown();
-  if (mFirstFrameCondVar) {
-    PR_DestroyCondVar(mFirstFrameCondVar);
-    mFirstFrameCondVar = nsnull;
-  }
-  if (mFirstFrameLock) {
-    PR_DestroyLock(mFirstFrameLock);
-    mFirstFrameLock = nsnull;
-  }
-}
+  NS_ASSERTION(mState > DECODER_STATE_DECODING_METADATA, "DisplayFrame() called during invalid state");
+  //  NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "DisplayFrame() called without acquiring decoder monitor");
+  int num_tracks = oggplay_get_num_tracks(mPlayer);
+  float audioTime = 0.0;
+  float videoTime = 0.0;
 
-nsIntSize nsOggDecoder::GetVideoSize(nsIntSize defaultSize)
-{
-  return (mRGBWidth == -1 || mRGBHeight == -1) ? defaultSize : nsIntSize(mRGBWidth, mRGBHeight);
-}
+  if (mVideoTrack != -1 && num_tracks > mVideoTrack) {
+    videoTime = DisplayTrack(mVideoTrack, aFrame[mVideoTrack], aAudioStream);
+  }
 
-double nsOggDecoder::GetVideoFramerate() {
-  return mFramerate;
-}
+  if (aAudioStream && mAudioTrack != -1 && num_tracks > mAudioTrack) {
+    audioTime = DisplayTrack(mAudioTrack, aFrame[mAudioTrack], aAudioStream);
+  }
 
-PRBool nsOggDecoder::IsPaused()
-{
-  return mPaused;
+  return videoTime > audioTime ? videoTime : audioTime;
 }
 
-PRBool nsOggDecoder::StepDecoding()
+float nsOggDecodeStateMachine::DisplayInitialFrame()
 {
-  PRBool stop = PR_TRUE;
-  if (mPlayer && mDecodeThread) {
-    OggPlayErrorCode r = oggplay_step_decoding(mPlayer);
-    if (r != E_OGGPLAY_CONTINUE && 
+  //  NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "DisplayInitialFrame() called without acquiring decoder monitor");
+  float time = 0.0;
+  OggPlayCallbackInfo **frame = NextFrame();
+  while (!frame) {
+    OggPlayErrorCode r = DecodeFrame();
+    if (r != E_OGGPLAY_CONTINUE &&
         r != E_OGGPLAY_USER_INTERRUPT &&
         r != E_OGGPLAY_TIMEOUT) {
-      stop = PR_TRUE;
-      // Inform the element that we've ended the video
-      nsCOMPtr<nsIRunnable> event = 
-        NS_NEW_RUNNABLE_METHOD(nsOggDecoder, this, PlaybackCompleted); 
-      
-      if (event) {
-        NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
-      }
+      break;
     }
-    else {
-      stop = PR_FALSE;
+
+    mBufferFull = (r == E_OGGPLAY_USER_INTERRUPT);
+    frame = NextFrame();
+  }
 
-      if (r == E_OGGPLAY_CONTINUE)
-        BufferData();
-    }
+  if (frame) {
+    time = DisplayFrame(frame, nsnull);
+    ReleaseFrame(frame);
+    mBufferFull = PR_FALSE;
   }
-  return !stop;
+  return time;
 }
 
-void nsOggDecoder::BufferData()
+float nsOggDecodeStateMachine::DisplayTrack(int aTrackNumber, OggPlayCallbackInfo* aTrack, nsAudioStream* aAudioStream)
 {
-  if (!mPaused && mReader && mMetadataLoaded && !mResourceLoaded) {    
-    double bps = mReader->BytesPerSecond();
-    PRUint32 bytes = static_cast<PRUint32>((bps * MAX_BUFFERING_DATA_WAIT_MS)/1000.0);
+  OggPlayDataType type = oggplay_callback_info_get_type(aTrack);
+  OggPlayDataHeader ** headers = oggplay_callback_info_get_headers(aTrack);
+  float time = 0.0;
 
-    // Buffer if it looks like we'll starve for data during
-    // MAX_BUFFERING_DATA_WAIT_MS time period.
-    if (mReader->Available() < bytes) {
-      PRIntervalTime start = PR_IntervalNow();
-      if (mElement) {
-        nsCOMPtr<nsIRunnable> event = 
-          NS_NEW_RUNNABLE_METHOD(nsOggDecoder, this, BufferingStarted);
-        if (event) 
-          // The pause event must be processed before the
-          // buffering loop occurs. Otherwise the loop exits
-          // immediately as it checks to see if the user chose
-          // to unpause. 
-          NS_DispatchToMainThread(event, NS_DISPATCH_SYNC);
+  switch(type) {
+  case OGGPLAY_INACTIVE:
+    {
+      break;
+    }
+    
+  case OGGPLAY_YUV_VIDEO:
+    {
+      time = ((float)oggplay_callback_info_get_presentation_time(headers[0]))/1000.0;          
+      OggPlayVideoData* video_data = oggplay_callback_info_get_video_data(headers[0]);
+      HandleVideoData(aTrackNumber, video_data);
+    }
+    break;
+  case OGGPLAY_FLOATS_AUDIO:
+    {
+      time = ((float)oggplay_callback_info_get_presentation_time(headers[0]))/1000.0;
+      int required = oggplay_callback_info_get_required(aTrack);
+      for (int j = 0; j < required; ++j) {
+        int size = oggplay_callback_info_get_record_size(headers[j]);
+        OggPlayAudioData* audio_data = oggplay_callback_info_get_audio_data(headers[j]);
+        HandleAudioData(aAudioStream, audio_data, size);
       }
-      
-      // Sleep to allow more data to be downloaded. Note that we are
-      // sleeping the decode thread, not the main thread. We stop
-      // sleeping when the resource is completely loaded, or the user
-      // explicitly chooses to continue playing, or an arbitary time
-      // period has elapsed or we've downloaded enough bytes to
-      // continue playing at the current download rate.
-      while (!mResourceLoaded && 
-             mPaused && 
-             (PR_IntervalToMilliseconds(PR_IntervalNow() - start) < MAX_BUFFERING_TIME_WAIT_MS) &&
-             mReader->Available() < bytes) {
-        LOG(PR_LOG_DEBUG, 
-            ("Buffering data until %d bytes available or %d milliseconds", 
-             (long)(bytes - mReader->Available()),
-             MAX_BUFFERING_TIME_WAIT_MS - (PR_IntervalToMilliseconds(PR_IntervalNow() - start))));
-        
-        PR_Sleep(PR_MillisecondsToInterval(1000));
-        
-        bps = mReader->BytesPerSecond();       
-        bytes = static_cast<PRUint32>((bps * (MAX_BUFFERING_DATA_WAIT_MS))/1000.0);
+      break;
+    }
+  case OGGPLAY_CMML:
+    {
+      if (oggplay_callback_info_get_required(aTrack) > 0) {
+        LOG(PR_LOG_DEBUG, ("CMML: %s", oggplay_callback_info_get_text_data(headers[0])));
       }
+      break;
+    }
+  default:
+    break;
+  }
+  return time;
+}
 
-      if (mElement) {
-        nsCOMPtr<nsIRunnable> event = 
-          NS_NEW_RUNNABLE_METHOD(nsOggDecoder, this, BufferingStopped);
-        if (event) 
-          NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
-      }
+void nsOggDecodeStateMachine::HandleVideoData(int aTrackNum, OggPlayVideoData* aVideoData) {
+  CopyVideoFrame(aTrackNum, aVideoData, mFramerate);
+
+  nsCOMPtr<nsIRunnable> event = 
+    NS_NEW_RUNNABLE_METHOD(nsOggDecoder, mDecoder, Invalidate); 
+  
+  NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
+}
+
+void nsOggDecodeStateMachine::HandleAudioData(nsAudioStream* aAudioStream, OggPlayAudioData* aAudioData, int aSize) {
+  if (aAudioStream) {
+    // 'aSize' is number of samples. Multiply by number of channels to
+    // get the actual number of floats being sent.
+    nsresult rv = aAudioStream->Write(reinterpret_cast<float*>(aAudioData), aSize * mAudioChannels);
+    if (!NS_SUCCEEDED(rv)) {
+      LOG(PR_LOG_ERROR, ("Could not write audio data to pipe"));
     }
   }
 }
 
-nsresult nsOggDecoder::Load(nsIURI* aURI) 
+void nsOggDecodeStateMachine::CopyVideoFrame(int aTrackNum, OggPlayVideoData* aVideoData, float aFramerate) 
 {
-  nsresult rv;
-  Stop();
-  mURI = aURI;
-
-  mReader = new nsChannelReader();
-  NS_ENSURE_TRUE(mReader, NS_ERROR_OUT_OF_MEMORY);
-
-  rv = mReader->Init(this, aURI);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  rv = NS_NewThread(getter_AddRefs(mDecodeThread));
-  NS_ENSURE_SUCCESS(rv, rv);
-  rv = NS_NewThread(getter_AddRefs(mPresentationThread));
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  mLoadInProgress = PR_TRUE;
-  nsCOMPtr<nsIRunnable> event = 
-    NS_NEW_RUNNABLE_METHOD(nsOggDecoder, this, LoadOggHeaders); 
-  NS_ENSURE_TRUE(event, NS_ERROR_OUT_OF_MEMORY);
-
-  rv = mDecodeThread->Dispatch(event, NS_DISPATCH_NORMAL);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  StartProgress();
+  int y_width;
+  int y_height;
+  oggplay_get_video_y_size(mPlayer, aTrackNum, &y_width, &y_height);
+  int uv_width;
+  int uv_height;
+  oggplay_get_video_uv_size(mPlayer, aTrackNum, &uv_width, &uv_height);
 
-  return NS_OK;
-}
-
-nsresult nsOggDecoder::Play()
-{
-  mPaused = PR_FALSE;
-  if (mLoadInProgress) {
-    mPlayAfterLoad = PR_TRUE;
-    return NS_OK;
-  }
-  else if (!mPlayer) {
-    Load(mURI);
-  }
-  else {
-    StartPlaybackThreads();
-  }
-
-  if (!mNotifyOnShutdown) {
-    nsCOMPtr<nsIObserverService> observerService =
-      do_GetService("@mozilla.org/observer-service;1");
-    if (observerService) {
-      mNotifyOnShutdown = 
-        NS_SUCCEEDED(observerService->AddObserver(this, 
-                                                  NS_XPCOM_SHUTDOWN_OBSERVER_ID, 
-                                                  PR_FALSE));
-    }
-    else {
-      NS_WARNING("Could not get an observer service. Video decoding events may not shutdown cleanly.");
-    }
+  if (y_width >= MAX_VIDEO_WIDTH || y_height >= MAX_VIDEO_HEIGHT) {
+    return;
   }
 
-  return NS_OK;
-} 
-
-nsresult nsOggDecoder::Seek(float time)
-{
-  return NS_ERROR_NOT_IMPLEMENTED;  
-}
-
-nsresult nsOggDecoder::PlaybackRateChanged()
-{
-  return NS_ERROR_NOT_IMPLEMENTED;
-}
-
-void nsOggDecoder::Stop()
-{
-  mLoadInProgress = PR_FALSE;
-  StopInvalidating();
-  StopProgress();
-  if (mDecodeThread) {
-    if (mPlayer) {
-      oggplay_prepare_for_close(mPlayer);
-    }
-    mDecodeThread->Shutdown();
-    mDecodeThread = nsnull;
-  }
-  if (mPresentationThread) {
-    if (!mFirstFrameLoaded) {
-      nsAutoLock lock(mFirstFrameLock);
-      mFirstFrameLoaded = PR_TRUE;
-      PR_NotifyAllCondVar(mFirstFrameCondVar);
-    }
+  {
+    nsAutoLock lock(mDecoder->mVideoUpdateLock);
 
-    mPresentationThread->Shutdown();
-    mPresentationThread = nsnull;
-  }
-  CloseAudioStream();
-  if (mPlayer){
-    oggplay_close(mPlayer);
-    mPlayer = nsnull;
-  }
-  mPaused = PR_TRUE;
-  mVideoCurrentFrameTime = 0.0;
-
-  if (mNotifyOnShutdown) {
-    nsCOMPtr<nsIObserverService> observerService =
-      do_GetService("@mozilla.org/observer-service;1");
-    if (observerService) {
-      mNotifyOnShutdown = PR_FALSE;
-      observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
-    }
-  }
-}
-
-void nsOggDecoder::HandleVideoData(int track_num, OggPlayVideoData* video_data) {
-  int y_width;
-  int y_height;
-  oggplay_get_video_y_size(mPlayer, track_num, &y_width, &y_height);
-
-  int uv_width;
-  int uv_height;
-  oggplay_get_video_uv_size(mPlayer, track_num, &uv_width, &uv_height);
-
-  if (y_width >= MAX_VIDEO_WIDTH || y_height >= MAX_VIDEO_HEIGHT)
-    return;
-
-  {
-    nsAutoLock lock(mVideoUpdateLock);
-
-    SetRGBData(y_width, y_height, mFramerate, nsnull);
+    mDecoder->SetRGBData(y_width, y_height, aFramerate, nsnull);
 
     // If there is not enough memory to allocate the RGB buffer,
     // don't display it, but continue without error. When enough
     // memory is available the display will start working again.
-    if (mRGB) {
+    if (mDecoder->mRGB) {
       OggPlayYUVChannels yuv;
       OggPlayRGBChannels rgb;
       
-      yuv.ptry = video_data->y;
-      yuv.ptru = video_data->u;
-      yuv.ptrv = video_data->v;
+      yuv.ptry = aVideoData->y;
+      yuv.ptru = aVideoData->u;
+      yuv.ptrv = aVideoData->v;
       yuv.uv_width = uv_width;
       yuv.uv_height = uv_height;
       yuv.y_width = y_width;
       yuv.y_height = y_height;
       
-      rgb.ptro = mRGB.get();
-      rgb.rgb_width = mRGBWidth;
-      rgb.rgb_height = mRGBHeight;
+      rgb.ptro = mDecoder->mRGB.get();
+      rgb.rgb_width = mDecoder->mRGBWidth;
+      rgb.rgb_height = mDecoder->mRGBHeight;
 
       oggplay_yuv2bgr(&yuv, &rgb);
     }
   }
 }
 
-void nsOggDecoder::HandleAudioData(OggPlayAudioData* audio_data, int size) {
-  if (mAudioStream) {
-    // 'size' is number of samples. Multiply by number of channels
-    // to get the actual number of floats being sent.
-    nsresult rv = mAudioStream->Write(reinterpret_cast<float*>(audio_data), size * mAudioChannels);
-    if (!NS_SUCCEEDED(rv)) {
-      LOG(PR_LOG_ERROR, ("Could not write audio data to pipe"));
-    }
+
+void nsOggDecodeStateMachine::Shutdown()
+{
+  // oggplay_prepare_for_close cannot be undone. Once called, the
+  // mPlayer object cannot decode any more frames. Once we've entered
+  // the shutdown state here there's no going back.
+  nsAutoMonitor mon(mDecoder->GetMonitor());
+  oggplay_prepare_for_close(mPlayer);
+  mState = DECODER_STATE_SHUTDOWN;
+  mon.NotifyAll();
+}
+
+void nsOggDecodeStateMachine::Decode()
+{
+  // When asked to decode, switch to decoding only if
+  // we are currently buffering.
+  nsAutoMonitor mon(mDecoder->GetMonitor());
+  if (mState == DECODER_STATE_BUFFERING) {
+    mState = DECODER_STATE_DECODING;
+    mon.NotifyAll();
   }
 }
 
-double nsOggDecoder::GetSyncTime() 
+void nsOggDecodeStateMachine::Seek(float aTime)
 {
-  double time = 0.0;
-  if (mAudioStream && mAudioTrack != -1) {
-    mAudioStream->GetTime(&time);
-  }
-  else {
-    // No audio stream, or audio track. Sync to system clock.
-    time = 
-      (mSystemSyncSeconds == 0.0) ?
-      0.0 :
-      double(PR_IntervalToMilliseconds(PR_IntervalNow()))/1000.0 - mSystemSyncSeconds;
-  }
-
-  return time;
+  nsAutoMonitor mon(mDecoder->GetMonitor());
+  mSeekTime = aTime;
+  mState = DECODER_STATE_SEEKING;
+  mon.NotifyAll();
 }
 
-void nsOggDecoder::OpenAudioStream()
+nsresult nsOggDecodeStateMachine::Run()
 {
-  mAudioStream = new nsAudioStream();
-  if (!mAudioStream) {
-    LOG(PR_LOG_ERROR, ("Could not create audio stream"));
+  nsAutoMonitor mon(mDecoder->GetMonitor());
+
+  while (PR_TRUE) {
+    switch(mState) {
+    case DECODER_STATE_DECODING_METADATA:
+      mon.Exit();
+      LoadOggHeaders();
+      mon.Enter();
+      
+      if (mState == DECODER_STATE_DECODING_METADATA) {
+        mState = DECODER_STATE_DECODING_FIRSTFRAME;
+      }
+
+      mon.NotifyAll();
+      break;
+
+    case DECODER_STATE_DECODING_FIRSTFRAME:
+      {
+        mon.Exit();
+        LoadFirstFrame();
+        mon.Enter();
+
+        if (mState == DECODER_STATE_DECODING_FIRSTFRAME) {
+          mState = DECODER_STATE_DECODING;
+        }
+
+        mon.NotifyAll();
+      }
+      break;
+
+    case DECODER_STATE_DECODING:
+      {
+        if (mReader->DownloadRate() >= 0 &&
+            mReader->Available() < mReader->PlaybackRate() * BUFFERING_SECONDS_WATERMARK) {
+          mBufferingStart = PR_IntervalNow();
+          mBufferingBytes = PRUint32(BUFFERING_RATE(mReader->PlaybackRate()) * BUFFERING_WAIT);
+          mState = DECODER_STATE_BUFFERING;
+          mon.NotifyAll();
+
+          nsCOMPtr<nsIRunnable> event =
+            NS_NEW_RUNNABLE_METHOD(nsOggDecoder, mDecoder, BufferingStarted);
+          NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
+        }
+        else {
+          // If the oggplay buffer is full, wait until it is not so we don't
+          // block inside liboggplay.
+          while (mBufferFull && mState == DECODER_STATE_DECODING) {
+            mon.Wait();
+          }
+
+          // State could have changed while we were waiting
+          if (mState != DECODER_STATE_DECODING) {
+            continue;
+          }
+
+          mon.Exit();
+          OggPlayErrorCode r = DecodeFrame();
+          mon.Enter();
+
+          mBufferFull = (r == E_OGGPLAY_USER_INTERRUPT);
+
+          if (mState == DECODER_STATE_SHUTDOWN) {
+            continue;
+          }
+
+          if (r != E_OGGPLAY_CONTINUE && 
+              r != E_OGGPLAY_USER_INTERRUPT &&
+              r != E_OGGPLAY_TIMEOUT)  {
+            mState = DECODER_STATE_COMPLETED;
+          }
+          
+          mon.NotifyAll();
+        }
+      }
+      break;
+
+    case DECODER_STATE_SEEKING:
+      {
+        // During the seek, don't have a lock on the decoder state,
+        // otherwise long seek operations can block the main thread.
+        // The events dispatched to the main thread are SYNC calls.
+        // These calls are made outside of the decode monitor lock so
+        // it is safe for the main thread to makes calls that acquire
+        // the lock since it won't deadlock. We check the state when
+        // acquiring the lock again in case shutdown has occurred
+        // during the time when we didn't have the lock.
+        float seekTime = mSeekTime;
+        mon.Exit();
+        nsCOMPtr<nsIRunnable> startEvent = 
+          NS_NEW_RUNNABLE_METHOD(nsOggDecoder, mDecoder, SeekingStarted);
+        NS_DispatchToMainThread(startEvent, NS_DISPATCH_SYNC);
+        
+        oggplay_seek(mPlayer, ogg_int64_t(seekTime * 1000));
+
+        mon.Enter();
+        if (mState == DECODER_STATE_SHUTDOWN) {
+          continue;
+        }
+
+        mDecoder->mDisplayStateMachine->UpdateFrameTime(DisplayInitialFrame());
+        mon.Exit();
+        
+        nsCOMPtr<nsIRunnable> stopEvent = 
+          NS_NEW_RUNNABLE_METHOD(nsOggDecoder, mDecoder, SeekingStopped);
+        NS_DispatchToMainThread(stopEvent, NS_DISPATCH_SYNC);        
+        mon.Enter();
+
+        if (mState == DECODER_STATE_SEEKING && mSeekTime == seekTime) {
+          mState = DECODER_STATE_DECODING;
+        }
+        
+        mon.NotifyAll();
+      }
+      break;
+
+    case DECODER_STATE_BUFFERING:
+      if ((PR_IntervalToMilliseconds(PR_IntervalNow() - mBufferingStart) < BUFFERING_WAIT*1000) &&
+          mReader->DownloadRate() >= 0 &&            
+          mReader->Available() < mBufferingBytes) {
+        LOG(PR_LOG_DEBUG, 
+            ("Buffering data until %d bytes available or %d milliseconds", 
+             (long)(mBufferingBytes - mReader->Available()),
+             BUFFERING_WAIT*1000 - (PR_IntervalToMilliseconds(PR_IntervalNow() - mBufferingStart))));
+        
+        mon.Wait(PR_MillisecondsToInterval(1000));
+      }
+      else
+        mState = DECODER_STATE_DECODING;
+
+      if (mState == DECODER_STATE_SHUTDOWN) {
+        continue;
+      }
+
+      if (mState != DECODER_STATE_BUFFERING) {
+        nsCOMPtr<nsIRunnable> event = 
+          NS_NEW_RUNNABLE_METHOD(nsOggDecoder, mDecoder, BufferingStopped);
+        NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
+        mon.NotifyAll();
+      }
+
+      break;
+
+    case DECODER_STATE_COMPLETED:
+      mon.Wait();
+      break;
+
+    case DECODER_STATE_SHUTDOWN:
+      return NS_OK;
+    }
   }
-  else {
-    mAudioStream->Init(mAudioChannels, mAudioRate);
-    mAudioStream->SetVolume(mInitialVolume);
-  }
+
+  return NS_OK;
 }
 
-void nsOggDecoder::CloseAudioStream()
-{
-  if (mAudioStream) {
-    mAudioStream->Shutdown();
-    mAudioStream = nsnull;
-  }
-}
-
-void nsOggDecoder::LoadOggHeaders() 
+void nsOggDecodeStateMachine::LoadOggHeaders() 
 {
   LOG(PR_LOG_DEBUG, ("Loading Ogg Headers"));
-  mPlayer = oggplay_open_with_reader((OggPlayReader*)mReader);
+
+  mPlayer = oggplay_open_with_reader(mReader);
   if (mPlayer) {
     LOG(PR_LOG_DEBUG, ("There are %d tracks", oggplay_get_num_tracks(mPlayer)));
 
     for (int i = 0; i < oggplay_get_num_tracks(mPlayer); ++i) {
       LOG(PR_LOG_DEBUG, ("Tracks %d: %s", i, oggplay_get_track_typename(mPlayer, i)));
       if (mVideoTrack == -1 && oggplay_get_track_type(mPlayer, i) == OGGZ_CONTENT_THEORA) {
         oggplay_set_callback_num_frames(mPlayer, i, 1);
         mVideoTrack = i;
@@ -641,238 +780,500 @@ void nsOggDecoder::LoadOggHeaders()
       else if (mAudioTrack == -1 && oggplay_get_track_type(mPlayer, i) == OGGZ_CONTENT_VORBIS) {
         mAudioTrack = i;
         oggplay_set_offset(mPlayer, i, OGGPLAY_AUDIO_OFFSET);
         oggplay_get_audio_samplerate(mPlayer, i, &mAudioRate);
         oggplay_get_audio_channels(mPlayer, i, &mAudioChannels);
         LOG(PR_LOG_DEBUG, ("samplerate: %d, channels: %d", mAudioRate, mAudioChannels));
       }
       
-      if (oggplay_set_track_active(mPlayer, i) < 0) 
+      if (oggplay_set_track_active(mPlayer, i) < 0)  {
         LOG(PR_LOG_ERROR, ("Could not set track %d active", i));
+      }
     }
     
     if (mVideoTrack == -1) {
       oggplay_set_callback_num_frames(mPlayer, mAudioTrack, OGGPLAY_FRAMES_PER_CALLBACK);
     }
 
     oggplay_use_buffer(mPlayer, OGGPLAY_BUFFER_SIZE);
 
     // Inform the element that we've loaded the Ogg metadata
     nsCOMPtr<nsIRunnable> metadataLoadedEvent = 
-      NS_NEW_RUNNABLE_METHOD(nsOggDecoder, this, MetadataLoaded); 
+      NS_NEW_RUNNABLE_METHOD(nsOggDecoder, mDecoder, MetadataLoaded); 
+    
+    NS_DispatchToMainThread(metadataLoadedEvent, NS_DISPATCH_NORMAL);
+  }
+}
+
+void nsOggDecodeStateMachine::LoadFirstFrame()
+{
+  NS_ASSERTION(!mBufferFull, "Buffer before reading first frame");
+  if(DecodeFrame() == E_OGGPLAY_USER_INTERRUPT) {
+    nsAutoMonitor mon(mDecoder->GetMonitor());
+    mBufferFull = PR_TRUE;
+  }
+
+  nsCOMPtr<nsIRunnable> event = 
+    NS_NEW_RUNNABLE_METHOD(nsOggDecoder, mDecoder, FirstFrameLoaded); 
     
-    if (metadataLoadedEvent) {
-      NS_DispatchToMainThread(metadataLoadedEvent, NS_DISPATCH_NORMAL);
+  NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
+}
+
+nsOggDisplayStateMachine::nsOggDisplayStateMachine(nsOggDecoder* aDecoder) :
+  mDecoder(aDecoder),
+  mCurrentFrameTime(0.0),
+  mLastFrameTime(0),
+  mVolume(1.0)
+{
+}
+
+nsOggDisplayStateMachine::~nsOggDisplayStateMachine()
+{
+}
+
+float nsOggDisplayStateMachine::GetCurrentTime()
+{
+  //  NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "GetCurrentTime() called without acquiring decoder monitor");
+  return mCurrentFrameTime;
+}
+
+
+nsresult nsOggDisplayStateMachine::Run()
+{
+  nsOggDecodeStateMachine* sm = mDecoder->mDecodeStateMachine;
+  PRBool playing = PR_FALSE;
+  {
+    nsAutoMonitor mon(mDecoder->GetMonitor());
+
+    // Wait until the decode state machine has decoded the first frame
+    // before displaying the initial starting frame. This ensures that
+    // the mPlayer object has been created (required for displaying a
+    // frame) and that the relevant metadata is valid (frame size,
+    // rate, etc).
+    while (sm->GetState() <= nsOggDecodeStateMachine::DECODER_STATE_DECODING_FIRSTFRAME)
+      mon.Wait();
+
+    OggPlayCallbackInfo **frame = sm->NextFrame();
+    while (!frame) {
+      frame = sm->NextFrame();
+
+      // No Frame, wait for decode state machine to decode one
+      if (!frame) {
+        mon.Wait();
+      }
+
+      if (sm->IsCompleted()) {
+        sm->ReleaseFrame(frame);
+        mon.NotifyAll();
+        return NS_OK;
+      }
     }
+    mCurrentFrameTime = sm->DisplayFrame(frame, nsnull);
+    sm->ReleaseFrame(frame);
+    mon.NotifyAll();
+  }
+
+  while (PR_TRUE) {
+    nsAutoMonitor mon(mDecoder->GetMonitor());
+    nsOggDecoder::PlayState state = mDecoder->GetState();
 
-    // Load the first frame of data 
-    nsCOMPtr<nsIRunnable> firstFrameEvent = 
-      NS_NEW_RUNNABLE_METHOD(nsOggDecoder, this, LoadFirstFrame); 
-    
-    if (firstFrameEvent) {
-      NS_GetCurrentThread()->Dispatch(firstFrameEvent, NS_DISPATCH_NORMAL);
+    if (state == nsOggDecoder::PLAY_STATE_PLAYING) {
+      if (!playing) {
+        // Just changed to play state, resume audio.
+        StartAudio();
+        playing = PR_TRUE;
+        mLastFrameTime = PR_IntervalNow();
+      }
+      
+      // Synchronize video to framerate
+      PRIntervalTime target = PR_MillisecondsToInterval(PRInt64(1000.0 / sm->GetFramerate()));
+      PRIntervalTime diff = PR_IntervalNow() - mLastFrameTime;
+      while (diff < target) {
+        mon.Wait(target-diff);
+        if (mDecoder->GetState() >= nsOggDecoder::PLAY_STATE_ENDED) {
+          return NS_OK;
+        }
+        diff = PR_IntervalNow() - mLastFrameTime;
+      }
+      mLastFrameTime = PR_IntervalNow();
+      
+      OggPlayCallbackInfo **frame = sm->NextFrame();
+      if (frame) {
+        mCurrentFrameTime = sm->DisplayFrame(frame, mAudioStream);
+        sm->ReleaseFrame(frame);
+        mon.NotifyAll();
+      }
+      else {
+        if (sm->IsCompleted()) {
+          nsCOMPtr<nsIRunnable> event = 
+            NS_NEW_RUNNABLE_METHOD(nsOggDecoder, mDecoder, PlaybackEnded);
+            
+          NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);        
+          return NS_OK;
+        }
+
+        // Need to notify that we are about to wait, in case the
+        // decoder thread is blocked waiting for us.
+        mon.NotifyAll();
+
+        mon.Wait();
+      }
+    }
+    else {
+      // Paused
+      if (playing) {
+        // Just changed to paused state, stop audio.
+        StopAudio();
+        playing = PR_FALSE;
+      }
+      mon.Wait();
+    }
+    if (mDecoder->GetState() >= nsOggDecoder::PLAY_STATE_ENDED) {
+      return NS_OK;
+    }
+  }
+
+  return NS_OK;
+}
+
+void nsOggDisplayStateMachine::UpdateFrameTime(float aTime)
+{
+  mCurrentFrameTime = aTime;
+}
+
+void nsOggDisplayStateMachine::OpenAudioStream()
+{
+  //  NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "OpenAudioStream() called without acquiring decoder monitor");
+  nsOggDecodeStateMachine* sm = mDecoder->mDecodeStateMachine;
+  mAudioStream = new nsAudioStream();
+  if (!mAudioStream) {
+    LOG(PR_LOG_ERROR, ("Could not create audio stream"));
+  }
+  else {
+    mAudioStream->Init(sm->GetAudioChannels(), sm->GetAudioRate());
+    mAudioStream->SetVolume(mVolume);
+  }
+}
+
+void nsOggDisplayStateMachine::CloseAudioStream()
+{
+  //  NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "CloseAudioStream() called without acquiring decoder monitor");
+  if (mAudioStream) {
+    mAudioStream->Shutdown();
+    mAudioStream = nsnull;
+  }
+}
+
+void nsOggDisplayStateMachine::StartAudio()
+{
+  //  NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "StartAudio() called without acquiring decoder monitor");
+  nsOggDecodeStateMachine* sm = mDecoder->mDecodeStateMachine;
+  if (sm->HasAudio()) {
+    if (!mAudioStream) {
+      OpenAudioStream();
+    }
+    else if(mAudioStream) {
+      mAudioStream->Resume();
+    }
+  }
+}
+
+void nsOggDisplayStateMachine::StopAudio()
+{
+  //  NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "StopAudio() called without acquiring decoder monitor");
+  nsOggDecodeStateMachine* sm = mDecoder->mDecodeStateMachine;
+  if (sm->HasAudio()) {
+    if (mDecoder->GetState() == nsOggDecoder::PLAY_STATE_SEEKING) {
+      // Need to close the stream to ensure that audio data buffered
+      // from the old seek location does not play when seeking
+      // completes and audio is resumed.
+      CloseAudioStream();
+    }
+    else if (mAudioStream) {
+      mAudioStream->Pause();
     }
   }
 }
 
-// This is always run in the decoder thread
-void nsOggDecoder::LoadFirstFrame() 
+float nsOggDisplayStateMachine::GetVolume()
+{
+  //  NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "GetVolume() called without acquiring decoder monitor");
+  return mVolume;
+}
+
+void nsOggDisplayStateMachine::SetVolume(float volume)
 {
-  if (StepDecoding()) {
-    // Inform the element that we've loaded the first frame of data
-    nsCOMPtr<nsIRunnable> frameLoadedEvent = 
-      NS_NEW_RUNNABLE_METHOD(nsOggDecoder, this, FirstFrameLoaded); 
+  //  NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "SetVolume() called without acquiring decoder monitor");
+  if (mAudioStream) {
+    mAudioStream->SetVolume(volume);
+  }
+
+  mVolume = volume;
+}
+
+NS_IMPL_THREADSAFE_ISUPPORTS1(nsOggDecoder, nsIObserver)
 
-    if (frameLoadedEvent) {
-      NS_DispatchToMainThread(frameLoadedEvent, NS_DISPATCH_NORMAL);
-    }
-    nsCOMPtr<nsIRunnable> displayEvent = 
-      NS_NEW_RUNNABLE_METHOD(nsOggDecoder, this, DisplayFirstFrame); 
-    if (displayEvent) {
-      mPresentationThread->Dispatch(displayEvent, NS_DISPATCH_NORMAL);
-    }
+void nsOggDecoder::Pause() 
+{
+  nsAutoMonitor mon(mMonitor);
+  if (mPlayState == PLAY_STATE_SEEKING) {
+    mNextState = PLAY_STATE_PAUSED;
+    return;
+  }
+
+  ChangeState(PLAY_STATE_PAUSED);
+}
 
-    // Notify waiting presentation thread that it can start playing
-    {
-      nsAutoLock lock(mFirstFrameLock);
+float nsOggDecoder::GetVolume()
+{
+  nsAutoMonitor mon(mMonitor);
+  return mDisplayStateMachine ? mDisplayStateMachine->GetVolume() : mInitialVolume;
+}
 
-      mFirstFrameLoaded = PR_TRUE;
-      PR_NotifyAllCondVar(mFirstFrameCondVar);
-    }
-    mDecodeThread->Dispatch(mDecodeEvent, NS_DISPATCH_NORMAL);
+void nsOggDecoder::SetVolume(float volume)
+{
+  nsAutoMonitor mon(mMonitor);
+  mInitialVolume = volume;
+
+  if (mDisplayStateMachine) {
+    mDisplayStateMachine->SetVolume(volume);
   }
 }
 
-void nsOggDecoder::StartPresentationThread() 
+float nsOggDecoder::GetDuration()
+{
+  // Currently not implemented. Video Spec says to return
+  // NaN if unknown.
+  // TODO: return NaN
+  return 0.0;
+}
+
+nsOggDecoder::nsOggDecoder() :
+  nsMediaDecoder(),
+  mBytesDownloaded(0),
+  mInitialVolume(0.0),
+  mSeekTime(-1.0),
+  mNotifyOnShutdown(PR_FALSE),
+  mReader(0),
+  mMonitor(0),
+  mPlayState(PLAY_STATE_PAUSED),
+  mNextState(PLAY_STATE_PAUSED)
+{
+  MOZ_COUNT_CTOR(nsOggDecoder);
+}
+
+PRBool nsOggDecoder::Init() 
+{
+  mMonitor = nsAutoMonitor::NewMonitor("media.decoder");
+  return mMonitor && nsMediaDecoder::Init();
+}
+
+void nsOggDecoder::Shutdown() 
+{
+  ChangeState(PLAY_STATE_SHUTDOWN);
+
+  Stop();
+  nsMediaDecoder::Shutdown();
+}
+
+nsOggDecoder::~nsOggDecoder()
 {
-  {
-    nsAutoLock lock(mFirstFrameLock);
+  MOZ_COUNT_DTOR(nsOggDecoder);
+  Shutdown();
+  nsAutoMonitor::DestroyMonitor(mMonitor);
+}
+
+nsresult nsOggDecoder::Load(nsIURI* aURI) 
+{
+  nsresult rv;
+  mURI = aURI;
+
+  StartProgress();
+
+  RegisterShutdownObserver();
+
+  mReader = new nsChannelReader();
+  NS_ENSURE_TRUE(mReader, NS_ERROR_OUT_OF_MEMORY);
+
+  rv = mReader->Init(this, aURI);
+  NS_ENSURE_SUCCESS(rv, rv);
 
-    while (!mFirstFrameLoaded)
-      PR_WaitCondVar(mFirstFrameCondVar, PR_INTERVAL_NO_TIMEOUT);
+  rv = NS_NewThread(getter_AddRefs(mDecodeThread));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = NS_NewThread(getter_AddRefs(mDisplayThread));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  mDecodeStateMachine = new nsOggDecodeStateMachine(this, mReader);
+
+  rv = mDecodeThread->Dispatch(mDecodeStateMachine, NS_DISPATCH_NORMAL);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  ChangeState(PLAY_STATE_LOADING);
+
+  return NS_OK;
+}
+
+nsresult nsOggDecoder::Play()
+{
+  nsAutoMonitor mon(mMonitor);
+  if (mPlayState == PLAY_STATE_SEEKING) {
+    mNextState = PLAY_STATE_PLAYING;
+    return NS_OK;
   }
 
-  if (mAudioStream) {
-    mAudioStream->Resume();
+  ChangeState(PLAY_STATE_PLAYING);
+
+  return NS_OK;
+}
+
+nsresult nsOggDecoder::Seek(float aTime)
+{
+  nsAutoMonitor mon(mMonitor);
+
+  if (aTime < 0.0)
+    return NS_ERROR_FAILURE;
+
+  if (mPlayState == PLAY_STATE_LOADING && aTime == 0.0) {
+    return NS_OK;
+  }
+
+  mSeekTime = aTime;
+
+  // If we are already in the seeking state, then setting mSeekTime
+  // above will result in the new seek occurring when the current seek
+  // completes.
+  if (mPlayState != PLAY_STATE_SEEKING) {
+    mNextState = mPlayState;
+    ChangeState(PLAY_STATE_SEEKING);
   }
-  else {
-    OpenAudioStream();
+
+  return NS_OK;
+}
+
+nsresult nsOggDecoder::PlaybackRateChanged()
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+void nsOggDecoder::Stop()
+{
+  ChangeState(PLAY_STATE_ENDED);
+
+  StopProgress();
+
+  // Force any outstanding seek and byterange requests to complete
+  // to prevent shutdown from deadlocking.
+  if (mReader) {
+    mReader->Cancel();
+    mReader = nsnull;
+  }
+
+  // Shutdown must be called on both mDecodeStateMachine and
+  // mDisplayStateMachine before deleting these objects.  This is
+  // required to ensure that the state machines aren't running in
+  // their thread and using internal objects when they are deleted.
+  if (mDecodeStateMachine) {
+    mDecodeStateMachine->Shutdown();
+  }
+
+  // The state machines must be Shutdown() before the thread is
+  // Shutdown. The Shutdown() on the state machine unblocks any
+  // blocking calls preventing the thread Shutdown from deadlocking.
+  if (mDecodeThread) {
+    mDecodeThread->Shutdown();
+    mDecodeThread = nsnull;
+  }
+
+  if (mDisplayThread) {
+    mDisplayThread->Shutdown();
+    mDisplayThread = nsnull;
   }
   
-  mSystemSyncSeconds = double(PR_IntervalToMilliseconds(PR_IntervalNow()))/1000.0;
-  mPresentationThread->Dispatch(mPresentationEvent, NS_DISPATCH_NORMAL);
+  mDecodeStateMachine = nsnull;
+  mDisplayStateMachine = nsnull;
+
+  UnregisterShutdownObserver();
 }
- 
+
+
+
 float nsOggDecoder::GetCurrentTime()
 {
-  return mVideoCurrentFrameTime;
+  nsAutoMonitor mon(mMonitor);
+
+  if (!mDisplayStateMachine) {
+    return 0.0;
+  }
+
+  return mDisplayStateMachine->GetCurrentTime();
 }
 
 void nsOggDecoder::GetCurrentURI(nsIURI** aURI)
 {
   NS_IF_ADDREF(*aURI = mURI);
 }
 
 nsIPrincipal* nsOggDecoder::GetCurrentPrincipal()
 {
-  if (!mReader)
+  if (!mReader) {
     return nsnull;
+  }
 
   return mReader->GetCurrentPrincipal();
 }
 
-void nsOggDecoder::DisplayFirstFrame()
-{
-  // Step through the decoded frames, allowing display of the first frame
-  // of video.
-  StepDisplay();
-}
-
-void nsOggDecoder::ProcessTrack(int aTrackNumber, OggPlayCallbackInfo* aTrackInfo)
-{
-  OggPlayDataType type = oggplay_callback_info_get_type(aTrackInfo);
-  OggPlayDataHeader ** headers = oggplay_callback_info_get_headers(aTrackInfo);
-  switch(type) {
-  case OGGPLAY_INACTIVE:
-    {
-      break;
-    }
-    
-  case OGGPLAY_YUV_VIDEO:
-    {
-      double video_time = ((double)oggplay_callback_info_get_presentation_time(headers[0]))/1000.0;
-      mVideoCurrentFrameTime = video_time;
-          
-      OggPlayVideoData* video_data = oggplay_callback_info_get_video_data(headers[0]);
-      HandleVideoData(aTrackNumber, video_data);
-    }
-    break;
-  case OGGPLAY_FLOATS_AUDIO:
-    {
-      int required = oggplay_callback_info_get_required(aTrackInfo);
-      for (int j = 0; j < required; ++j) {
-        int size = oggplay_callback_info_get_record_size(headers[j]);
-        OggPlayAudioData* audio_data = oggplay_callback_info_get_audio_data(headers[j]);
-        HandleAudioData(audio_data, size);
-      }
-      break;
-    }
-  case OGGPLAY_CMML:
-    {
-      if (oggplay_callback_info_get_required(aTrackInfo) > 0)
-        LOG(PR_LOG_DEBUG, ("CMML: %s", oggplay_callback_info_get_text_data(headers[0])));
-      break;
-    }
-  default:
-    break;
-  }
-}
-
-PRBool nsOggDecoder::StepDisplay()
-{
-  if (!mPlayer/* || !mDecodeThread */) {
-    return PR_FALSE;
-  }
-
-  int num_tracks = oggplay_get_num_tracks(mPlayer);
-  OggPlayCallbackInfo  ** track_info = oggplay_buffer_retrieve_next(mPlayer);
-
-  if (track_info) {
-   double audio_time = GetSyncTime();
-    PRInt32 millis = PRInt32((mVideoNextFrameTime-audio_time) * 1000.0);      
-    // LOG(PR_LOG_DEBUG, ("Sleep: %d %f %f", millis, audio_time, mVideoNextFrameTime));
-    mVideoNextFrameTime += 1.0/mFramerate;
-
-    if (millis > 0) {
-      PR_Sleep(PR_MillisecondsToInterval(millis));
-    }
-
-   if (mVideoTrack != -1 && num_tracks > mVideoTrack)  {
-      ProcessTrack(mVideoTrack, track_info[mVideoTrack]);
-   }
-
-   if (mAudioTrack != -1 && num_tracks > mAudioTrack) {
-      ProcessTrack(mAudioTrack, track_info[mAudioTrack]);
-   }
-
-   oggplay_buffer_release(mPlayer, track_info);
-  }
-  else {
-    PR_Sleep(PR_MillisecondsToInterval(10));
-  }
-
-  return PR_TRUE;
-}
-
-void nsOggDecoder::StartPlaybackThreads()
-{
-  StartInvalidating(mFramerate);
-
-  nsCOMPtr<nsIRunnable> event = 
-    NS_NEW_RUNNABLE_METHOD(nsOggDecoder, this, StartPresentationThread); 
-  if (event)
-    mPresentationThread->Dispatch(event, NS_DISPATCH_NORMAL);
-}
-
 void nsOggDecoder::MetadataLoaded()
 {
-  mMetadataLoaded = PR_TRUE;
   if (mElement) {
     mElement->MetadataLoaded();
   }
 
-  if (mPlayAfterLoad) {
-    mPlayAfterLoad = PR_FALSE;
-    StartPlaybackThreads();
-  }
-  mLoadInProgress = PR_FALSE;
+  mDisplayStateMachine = new nsOggDisplayStateMachine(this);
+  mDisplayThread->Dispatch(mDisplayStateMachine, NS_DISPATCH_NORMAL);
 }
 
 void nsOggDecoder::FirstFrameLoaded()
 {
-  StartInvalidating(mFramerate);
   if (mElement) {
     mElement->FirstFrameLoaded();
   }
+
+  // The element can run javascript via events
+  // before reaching here, so only change the 
+  // state if we're still set to the original
+  // loading state.
+  nsAutoMonitor mon(mMonitor);
+  if (mPlayState == PLAY_STATE_LOADING) {
+    if (mSeekTime >= 0.0)
+      ChangeState(PLAY_STATE_SEEKING);
+    else
+      ChangeState(mNextState);
+  }
 }
 
 void nsOggDecoder::ResourceLoaded()
 {
-  mResourceLoaded = PR_TRUE;
   if (mElement) {
     mElement->ResourceLoaded();
   }
   StopProgress();
 }
 
-void nsOggDecoder::PlaybackCompleted()
+PRBool nsOggDecoder::IsSeeking() const
 {
-  if (mElement) {
-    mElement->PlaybackCompleted();
+  return mPlayState == PLAY_STATE_SEEKING;
+}
+
+void nsOggDecoder::PlaybackEnded()
+{
+  Stop();
+  if (mElement)  {
+    mElement->PlaybackEnded();
   }
 }
 
 NS_IMETHODIMP nsOggDecoder::Observe(nsISupports *aSubjet,
                                       const char *aTopic,
                                       const PRUnichar *someData)
 {
   if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
@@ -897,19 +1298,131 @@ void nsOggDecoder::UpdateBytesDownloaded
 {
   mBytesDownloaded = aBytes;
 }
 
 void nsOggDecoder::BufferingStopped()
 {
   if (mElement) {
     mElement->ChangeReadyState(nsIDOMHTMLMediaElement::CAN_SHOW_CURRENT_FRAME);
-    mElement->Play();
   }
 }
 
 void nsOggDecoder::BufferingStarted()
 {
   if (mElement) {
-    mElement->Pause();
     mElement->ChangeReadyState(nsIDOMHTMLMediaElement::DATA_UNAVAILABLE);
   }
 }
+
+void nsOggDecoder::SeekingStopped()
+{
+  {
+    nsAutoMonitor mon(mMonitor);
+    if (mPlayState == PLAY_STATE_SHUTDOWN)
+      return;
+
+    // An additional seek was requested while the current seek was
+    // in operation.
+    if (mSeekTime >= 0.0)
+      ChangeState(PLAY_STATE_SEEKING);
+    else
+      ChangeState(mNextState);
+  }
+
+  if (mElement) {
+    mElement->SeekCompleted();
+  }
+}
+
+void nsOggDecoder::SeekingStarted()
+{
+  {
+    nsAutoMonitor mon(mMonitor);
+    if (mPlayState == PLAY_STATE_SHUTDOWN)
+      return;
+  }
+
+  if (mElement) {
+    mElement->SeekStarted();
+  }
+}
+
+void nsOggDecoder::RegisterShutdownObserver()
+{
+  if (!mNotifyOnShutdown) {
+    nsCOMPtr<nsIObserverService> observerService =
+      do_GetService("@mozilla.org/observer-service;1");
+    if (observerService) {
+      mNotifyOnShutdown = 
+        NS_SUCCEEDED(observerService->AddObserver(this, 
+                                                  NS_XPCOM_SHUTDOWN_OBSERVER_ID, 
+                                                  PR_FALSE));
+    }
+    else {
+      NS_WARNING("Could not get an observer service. Video decoding events may not shutdown cleanly.");
+    }
+  }
+}
+
+void nsOggDecoder::UnregisterShutdownObserver()
+{
+  if (mNotifyOnShutdown) {
+    nsCOMPtr<nsIObserverService> observerService =
+      do_GetService("@mozilla.org/observer-service;1");
+    if (observerService) {
+      mNotifyOnShutdown = PR_FALSE;
+      observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+    }
+  }
+}
+
+void nsOggDecoder::ChangeState(PlayState aState)
+{
+  nsAutoMonitor mon(mMonitor);
+
+  if (mNextState == aState) {
+    mNextState = PLAY_STATE_PAUSED;
+  }
+
+  if (mPlayState == PLAY_STATE_SHUTDOWN) {
+    return;
+  }
+
+  if (mPlayState == PLAY_STATE_ENDED &&
+      aState != PLAY_STATE_SHUTDOWN) {
+    // If we've completed playback then the decode and display threads
+    // have been shutdown. To honor the state change request we need
+    // to reload the resource and restart the threads.
+    mNextState = aState;
+    mPlayState = PLAY_STATE_LOADING;
+    Load(mURI);
+    return;
+  }
+
+  mPlayState = aState;
+  switch (aState) {
+  case PLAY_STATE_PAUSED:
+    /* No action needed */
+    break;
+  case PLAY_STATE_PLAYING:
+    mDecodeStateMachine->Decode();
+    break;
+  case PLAY_STATE_SEEKING:
+    mDecodeStateMachine->Seek(mSeekTime);
+    mSeekTime = -1.0;
+    break;
+  case PLAY_STATE_LOADING:
+    /* No action needed */
+    break;
+  case PLAY_STATE_START:
+    /* No action needed */
+    break;
+  case PLAY_STATE_ENDED:
+    /* No action needed */
+    break;
+  case PLAY_STATE_SHUTDOWN:
+    /* No action needed */
+    break;
+  }
+  mon.NotifyAll();
+}
+
deleted file mode 100644
--- a/content/media/video/src/nsVideoDecoder.cpp
+++ /dev/null
@@ -1,260 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim:set ts=2 sw=2 sts=2 et cindent: */
-/* ***** BEGIN LICENSE BLOCK *****
- * Version: ML 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (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.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is Mozilla code.
- *
- * The Initial Developer of the Original Code is the Mozilla Corporation.
- * Portions created by the Initial Developer are Copyright (C) 2007
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *  Chris Double <chris.double@double.co.nz>
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either the GNU General Public License Version 2 or later (the "GPL"), or
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
-#include "prlog.h"
-#include "prmem.h"
-#include "nsIFrame.h"
-#include "nsIDocument.h"
-#include "nsThreadUtils.h"
-#include "nsIDOMHTMLMediaElement.h"
-#include "nsNetUtil.h"
-#include "nsHTMLMediaElement.h"
-#include "nsIObserver.h"
-#include "nsIObserverService.h"
-#include "nsAutoLock.h"
-#include "nsIRenderingContext.h"
-#include "gfxContext.h"
-#include "gfxImageSurface.h"
-#include "nsPresContext.h"
-#include "nsVideoDecoder.h"
-
-#ifdef PR_LOGGING
-// Logging object for decoder
-PRLogModuleInfo* gVideoDecoderLog = nsnull;
-#endif
-
-nsVideoDecoder::nsVideoDecoder() :
-  mElement(0),
-  mRGBWidth(-1),
-  mRGBHeight(-1),
-  mSizeChanged(PR_FALSE),
-  mVideoUpdateLock(nsnull),
-  mFramerate(0.0)
-{
-}
-
-PRBool nsVideoDecoder::Init()
-{
-  mVideoUpdateLock = PR_NewLock();
-
-  return mVideoUpdateLock != nsnull;
-}
-
-void nsVideoDecoder::Shutdown()
-{
-  if (mVideoUpdateLock) {
-    PR_DestroyLock(mVideoUpdateLock);
-    mVideoUpdateLock = nsnull;
-  }
-}
-
-
-nsresult nsVideoDecoder::InitLogger() 
-{
-#ifdef PR_LOGGING
-  gVideoDecoderLog = PR_NewLogModule("nsVideoDecoder");
-#endif
-  return NS_OK;
-}
-
-static void InvalidateCallback(nsITimer* aTimer, void* aClosure)
-{
-  nsVideoDecoder* decoder = static_cast<nsVideoDecoder*>(aClosure);
-  decoder->Invalidate();
-}
-
-nsresult nsVideoDecoder::StartInvalidating(double aFramerate)
-{
-  nsresult rv = NS_OK;
-
-  if (!mInvalidateTimer && aFramerate > 0.0) {
-    mInvalidateTimer = do_CreateInstance("@mozilla.org/timer;1");
-    rv = mInvalidateTimer->InitWithFuncCallback(InvalidateCallback, 
-                                                this, 
-                                                static_cast<PRInt32>(1000.0/aFramerate), 
-                                                nsITimer::TYPE_REPEATING_PRECISE);
-  }
-  return rv;
-}
-
-void nsVideoDecoder::StopInvalidating()
-{
-  if (mInvalidateTimer) {
-    mInvalidateTimer->Cancel();
-    mInvalidateTimer = nsnull;
-  }
-}
-
-void nsVideoDecoder::Invalidate()
-{
-  if (!mElement)
-    return;
-
-  nsIFrame* frame = mElement->GetPrimaryFrame();
-  if (!frame)
-    return;
-  
-  {
-    nsAutoLock lock(mVideoUpdateLock);
-    if (mSizeChanged) {
-      mSizeChanged = PR_FALSE;
-      nsPresContext* presContext = frame->PresContext();      
-      nsIPresShell *presShell = presContext->PresShell();
-      presShell->FrameNeedsReflow(frame, 
-                                  nsIPresShell::eStyleChange,
-                                  NS_FRAME_IS_DIRTY);
-    }
-  }
-  nsRect r(nsPoint(0,0), frame->GetSize());
-  frame->Invalidate(r);
-}
-
-static void ProgressCallback(nsITimer* aTimer, void* aClosure)
-{
-  nsVideoDecoder* decoder = static_cast<nsVideoDecoder*>(aClosure);
-  decoder->Progress();
-}
-
-void nsVideoDecoder::Progress()
-{
-  if (!mElement)
-    return;
-
-  mElement->DispatchProgressEvent(NS_LITERAL_STRING("progress"));
-}
-
-nsresult nsVideoDecoder::StartProgress()
-{
-  nsresult rv = NS_OK;
-
-  if (!mProgressTimer) {
-    mProgressTimer = do_CreateInstance("@mozilla.org/timer;1");
-    rv = mProgressTimer->InitWithFuncCallback(ProgressCallback, 
-                                              this, 
-                                              350, // Number of milliseconds defined in spec
-                                              nsITimer::TYPE_REPEATING_PRECISE);
-  }
-  return rv;
-}
-
-nsresult nsVideoDecoder::StopProgress()
-{
-  nsresult rv = NS_OK;
-  if (mProgressTimer) {
-    rv = mProgressTimer->Cancel();
-    mProgressTimer = nsnull;
-  }
-  return rv;
-}
-
-void nsVideoDecoder::SetRGBData(PRInt32 aWidth, PRInt32 aHeight, double aFramerate, unsigned char* aRGBBuffer)
-{
-  if (mRGBWidth != aWidth || mRGBHeight != aHeight) {
-    mRGBWidth = aWidth;
-    mRGBHeight = aHeight;
-    mSizeChanged = PR_TRUE;
-    // Delete buffer so we'll reallocate it
-    mRGB = nsnull;
-  }
-  mFramerate = aFramerate;
-
-  if (!mRGB) 
-    mRGB = new unsigned char[aWidth * aHeight * 4];
-  if (mRGB && aRGBBuffer) {
-    memcpy(mRGB.get(), aRGBBuffer, aWidth*aHeight*4);
-  }
-}
-
-void nsVideoDecoder::Paint(gfxContext* aContext, const gfxRect& aRect)
-{
-  nsAutoLock lock(mVideoUpdateLock);
-
-  if (!mRGB)
-    return;
-
-  if (mFramerate > 0.0) {
-    StartInvalidating(mFramerate);
-  }
-
-  /* Create a surface backed by the RGB */
-  nsRefPtr<gfxASurface> surface = 
-    new gfxImageSurface(mRGB,
-                        gfxIntSize(mRGBWidth, mRGBHeight), 
-                        mRGBWidth * 4,
-                        gfxASurface::ImageFormatARGB32);    
-
-  if (!surface)
-    return;
-
-  nsRefPtr<gfxPattern> pat = new gfxPattern(surface);
-  if (!pat)
-    return;
-
-  // Make the source image fill the rectangle completely
-  pat->SetMatrix(gfxMatrix().Scale(mRGBWidth/aRect.Width(), mRGBHeight/aRect.Height()));
-
-  /* Draw RGB surface onto frame */
-  aContext->NewPath();
-  aContext->PixelSnappedRectangleAndSetPattern(aRect, pat);
-  aContext->Fill();
-
-#ifdef DEBUG_FRAME_RATE
-  {
-    // Output frame rate
-    static double last = double(PR_IntervalToMilliseconds(PR_IntervalNow()))/1000.0;
-    double now = double(PR_IntervalToMilliseconds(PR_IntervalNow()))/1000.0;
-    static int count = 0;
-    count++;
-    if (now-last > 10.0) {
-      LOG(PR_LOG_DEBUG, ("Paint Frame Rate = %f (should be %f)\n", (float)count / (float)(now-last), mFramerate));
-      count = 0;
-      last = double(PR_IntervalToMilliseconds(PR_IntervalNow()))/1000.0;
-    }
-  }   
-#endif
-}
-
-void nsVideoDecoder::ElementAvailable(nsHTMLMediaElement* anElement)
-{
-  mElement = anElement;
-}
-
-void nsVideoDecoder::ElementUnavailable()
-{
-  mElement = nsnull;
-}
-
--- a/layout/build/nsLayoutStatics.cpp
+++ b/layout/build/nsLayoutStatics.cpp
@@ -106,22 +106,21 @@ PRBool NS_SVGEnabled();
 #endif
 
 #ifndef MOZILLA_PLAINTEXT_EDITOR_ONLY
 #include "nsHTMLEditor.h"
 #include "nsTextServicesDocument.h"
 #endif
 
 #ifdef MOZ_MEDIA
-#include "nsVideoDecoder.h"
+#include "nsMediaDecoder.h"
 #endif
 
 #ifdef MOZ_OGG
 #include "nsAudioStream.h"
-#include "nsVideoDecoder.h"
 #endif
 
 #include "nsError.h"
 #include "nsTraceRefcnt.h"
 
 #include "nsCycleCollector.h"
 #include "nsJSEnvironment.h"
 
@@ -241,19 +240,19 @@ nsLayoutStatics::Initialize()
   rv = nsXULPopupManager::Init();
   if (NS_FAILED(rv)) {
     NS_ERROR("Could not initialize nsXULPopupManager");
     return rv;
   }
 #endif
 
 #ifdef MOZ_MEDIA
-  rv = nsVideoDecoder::InitLogger();
+  rv = nsMediaDecoder::InitLogger();
   if (NS_FAILED(rv)) {
-    NS_ERROR("Could not initialize nsVideoDecoder");
+    NS_ERROR("Could not initialize nsMediaDecoder");
     return rv;
   }
   
 #endif
 
 #ifdef MOZ_OGG
   rv = nsAudioStream::InitLibrary();
   if (NS_FAILED(rv)) {