Bug 635649 - Refactor Wave backend to use content/media nsBuiltinDecoder framework - r=kinetik
authorChris Double <chris.double@double.co.nz>
Wed, 30 Mar 2011 18:37:42 +1300
changeset 64403 c158f8b0a921bb74def0e6c2880891a60b407d69
parent 64402 3badbf0203543d72a1a6c295db9228ba11ce1474
child 64404 a65a0143d091253ffccc487d549c3dc280064da6
push idunknown
push userunknown
push dateunknown
reviewerskinetik
bugs635649
milestone2.2a1pre
Bug 635649 - Refactor Wave backend to use content/media nsBuiltinDecoder framework - r=kinetik commit c7e190d81b10e7425b53217352c126adfbb79c4a Author: Chris Double <chris.double@double.co.nz> Date: Thu Mar 24 18:09:06 2011 +1300 Fix
content/media/nsBuiltinDecoderReader.h
content/media/test/Makefile.in
content/media/test/test_autoplay_contentEditable.html
content/media/test/test_wave_data_s16.html
content/media/test/test_wave_data_u8.html
content/media/test/wavedata_s16.wav
content/media/test/wavedata_u8.wav
content/media/wave/Makefile.in
content/media/wave/nsWaveDecoder.cpp
content/media/wave/nsWaveDecoder.h
content/media/wave/nsWaveReader.cpp
content/media/wave/nsWaveReader.h
--- a/content/media/nsBuiltinDecoderReader.h
+++ b/content/media/nsBuiltinDecoderReader.h
@@ -114,25 +114,27 @@ typedef short SoundDataValue;
 
 #define MOZ_SOUND_DATA_FORMAT (nsAudioStream::FORMAT_S16_LE)
 #define MOZ_CLIP_TO_15(x) ((x)<-32768?-32768:(x)<=32767?(x):32767)
 // Convert the output of vorbis_synthesis_pcmout to a SoundDataValue
 #define MOZ_CONVERT_VORBIS_SAMPLE(x) \
  (static_cast<SoundDataValue>(MOZ_CLIP_TO_15((x)>>9)))
 // Convert a SoundDataValue to a float for the Audio API
 #define MOZ_CONVERT_SOUND_SAMPLE(x) ((x)*(1.F/32768))
+#define MOZ_SAMPLE_TYPE_S16LE 1
 
 #else /*MOZ_VORBIS*/
 
 typedef float VorbisPCMValue;
 typedef float SoundDataValue;
 
 #define MOZ_SOUND_DATA_FORMAT (nsAudioStream::FORMAT_FLOAT32)
 #define MOZ_CONVERT_VORBIS_SAMPLE(x) (x)
 #define MOZ_CONVERT_SOUND_SAMPLE(x) (x)
+#define MOZ_SAMPLE_TYPE_FLOAT32 1
 
 #endif
 
 // Holds chunk a decoded sound samples.
 class SoundData {
 public:
   SoundData(PRInt64 aOffset,
             PRInt64 aTime,
--- a/content/media/test/Makefile.in
+++ b/content/media/test/Makefile.in
@@ -224,16 +224,18 @@ endif
 		big.wav \
 		bogus.wav \
 		r11025_msadpcm_c1.wav \
 		r11025_s16_c1.wav \
 		r11025_s16_c1_trailing.wav \
 		r11025_u8_c1.wav \
 		r11025_u8_c1_trunc.wav \
 		r16000_u8_c1_list.wav \
+		wavedata_u8.wav \
+		wavedata_s16.wav \
 		$(NULL)
 
 # Other files
 _TEST_FILES += \
 		bogus.duh \
 		$(NULL)
 
 # These tests contain backend-specific tests. Try to write backend
@@ -275,16 +277,18 @@ else
 _TEST_FILES += \
 		test_can_play_type_no_webm.html \
 		$(NULL)
 endif
 
 ifdef MOZ_WAVE
 _TEST_FILES += \
 		test_can_play_type_wave.html \
+		test_wave_data_u8.html \
+		test_wave_data_s16.html \
 		$(NULL)
 else
 _TEST_FILES += \
 		test_can_play_type_no_wave.html \
 		$(NULL)
 endif
 
 libs:: $(_TEST_FILES)
--- a/content/media/test/test_autoplay_contentEditable.html
+++ b/content/media/test/test_autoplay_contentEditable.html
@@ -11,17 +11,17 @@
 <pre id="test">
 
 <script>
 
 var manager = new MediaTestManager;
 
 var tokens = {
   0:                ["canplay"],
-  "canplay":        ["canplaythrough"],
+  "canplay":        ["canplay", "canplaythrough"],
   "canplaythrough": ["canplay", "canplaythrough"]
 };
 
 function gotPlayEvent(event) {
   var v = event.target;
   ok(tokens[v._state].indexOf(event.type) >= 0,
      "Check expected event got " + event.type + " at " + v._state + " for " + v.src +
      " uneval(event.type)=" + uneval(event.type) + " typeof(event.type)=" + typeof(event.type) +
new file mode 100644
--- /dev/null
+++ b/content/media/test/test_wave_data_s16.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Wave Media test: ended</title>
+  <script type="text/javascript" src="/MochiKit/packed.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+// Test if the ended event works correctly.
+var endPassed = false;
+var completed = false;
+
+function audioavailable(e) {
+  if (completed)
+    return false;
+
+  completed = true;
+  var samples = e.frameBuffer;
+  var time = e.time;
+
+  ok(samples.length >= 3, "Must be 3 or more samples. There were " + samples.length);  
+  if (samples.length >= 3) {
+    ok(samples[0] > 0.99 && samples[0] < 1.01, "First sound sample should be close to 1.0. It was " + samples[0]);
+    ok(samples[1] > -1.01 && samples [1] < 0.01, "Second sound sample should be close to -1.0. It was " + samples[1]);
+    ok(samples[2] > -0.01 && samples[2] < 0.01, "Third sound sample should be close to 0. It was " + samples[2]);
+  }
+
+  // Only care about the first few samples
+  SimpleTest.finish();
+  return false;
+}
+
+function startTest() {
+  if (completed)
+    return false;
+  var v = document.getElementById('v');
+  v.addEventListener('MozAudioAvailable', audioavailable, false);
+  v.play();
+  return false;
+}
+
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+<audio id='v'
+       onloadedmetadata='return startTest();'>
+  <source type='audio/x-wav' src='wavedata_s16.wav'>
+</audio>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/content/media/test/test_wave_data_u8.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Wave Media test: ended</title>
+  <script type="text/javascript" src="/MochiKit/packed.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+// Test if the ended event works correctly.
+var endPassed = false;
+var completed = false;
+
+function audioavailable(e) {
+  if (completed)
+    return false;
+
+  completed = true;
+  var samples = e.frameBuffer;
+  var time = e.time;
+
+  ok(samples.length >= 3, "Must be 3 or more samples. There were " + samples.length);  
+  if (samples.length >= 3) {
+    ok(samples[0] > 0.99 && samples[0] < 1.01, "First sound sample should be close to 1.0. It was " + samples[0]);
+    ok(samples[1] > -1.01 && samples [1] < 0.01, "Second sound sample should be close to -1.0. It was " + samples[1]);
+    ok(samples[2] > -0.01 && samples[2] < 0.01, "Third sound sample should be close to 0. It was " + samples[2]);
+  }
+
+  // Only care about the first few samples
+  SimpleTest.finish();
+  return false;
+}
+
+function startTest() {
+  if (completed)
+    return false;
+  var v = document.getElementById('v');
+  v.addEventListener('MozAudioAvailable', audioavailable, false);
+  v.play();
+  return false;
+}
+
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+<audio id='v'
+       onloadedmetadata='return startTest();'>
+  <source type='audio/x-wav' src='wavedata_u8.wav'>
+</audio>
+</body>
+</html>
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..6a69cd78f6e29f9851f231d67837654a55d87a82
GIT binary patch
literal 22062
zc%1FYAr6B;07TI#C!jYVSWZ#FCbbCABm`H7hnu(CJ0M@4`I(vCrIa~YUh}wrj;>Ae
ssGdX8*S$%bZ9Ue1f6rX?z5oCK00000000000000000000006k#52p}`fdBvi
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..1d895c2ce0867c03ba384b84b81918fa51bbb12a
GIT binary patch
literal 11037
zc%1FXs||zz07SvXA)$lw2owb%Ai<shO>#MhqX0VR(*<JYWzBx4aqOC8>F43U?Vl=1
faaZ5pvm#}F>%P`)a^?sC00000000000KC=(p0)@M
--- a/content/media/wave/Makefile.in
+++ b/content/media/wave/Makefile.in
@@ -47,16 +47,17 @@ LIBXUL_LIBRARY 	= 1
 
 
 EXPORTS		+= \
 		nsWaveDecoder.h \
 		$(NULL)
 
 CPPSRCS		= \
 		nsWaveDecoder.cpp \
+		nsWaveReader.cpp \
 		$(NULL)
 
 FORCE_STATIC_LIB = 1
 
 include $(topsrcdir)/config/rules.mk
 
 INCLUDES	+= \
 		-I$(srcdir)/../../base/src \
--- a/content/media/wave/nsWaveDecoder.cpp
+++ b/content/media/wave/nsWaveDecoder.cpp
@@ -10,18 +10,18 @@
  *
  * 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) 2008
+ * The Initial Developer of the Original Code is the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *  Matthew Gregan <kinetik@flim.org>
  *
  * 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"),
@@ -30,1731 +30,16 @@
  * 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 "limits"
-#include "prlog.h"
-#include "prmem.h"
-#include "nsIDOMHTMLMediaElement.h"
-#include "nsIDocument.h"
-#include "nsIFrame.h"
-#include "nsIObserver.h"
-#include "nsISeekableStream.h"
-#include "nsAudioStream.h"
-#include "nsAutoLock.h"
-#include "nsHTMLMediaElement.h"
-#include "nsNetUtil.h"
-#include "nsThreadUtils.h"
+#include "nsBuiltinDecoderStateMachine.h"
+#include "nsWaveReader.h"
 #include "nsWaveDecoder.h"
-#include "nsTimeRanges.h"
-
-using mozilla::TimeDuration;
-using mozilla::TimeStamp;
-
-#ifdef PR_LOGGING
-static PRLogModuleInfo* gWaveDecoderLog;
-#define LOG(type, msg) PR_LOG(gWaveDecoderLog, type, msg)
-#else
-#define LOG(type, msg)
-#endif
-
-// Maximum number of seconds to wait when buffering.
-#define BUFFERING_TIMEOUT 3
-
-// Duration the playback loop will sleep after refilling the backend's audio
-// buffers.  The loop's goal is to keep AUDIO_BUFFER_LENGTH milliseconds of
-// audio buffered to allow time to refill before the backend underruns.
-// Should be a multiple of 10 to deal with poor timer granularity on some
-// platforms.
-#define AUDIO_BUFFER_WAKEUP 100
-#define AUDIO_BUFFER_LENGTH (2 * AUDIO_BUFFER_WAKEUP)
-
-// Magic values that identify RIFF chunks we're interested in.
-#define RIFF_CHUNK_MAGIC 0x52494646
-#define WAVE_CHUNK_MAGIC 0x57415645
-#define FRMT_CHUNK_MAGIC 0x666d7420
-#define DATA_CHUNK_MAGIC 0x64617461
-
-// Size of RIFF chunk header.  4 byte chunk header type and 4 byte size field.
-#define RIFF_CHUNK_HEADER_SIZE 8
-
-// Size of RIFF header.  RIFF chunk and 4 byte RIFF type.
-#define RIFF_INITIAL_SIZE (RIFF_CHUNK_HEADER_SIZE + 4)
-
-// Size of required part of format chunk.  Actual format chunks may be
-// extended (for non-PCM encodings), but we skip any extended data.
-#define WAVE_FORMAT_CHUNK_SIZE 16
-
-// PCM encoding type from format chunk.  Linear PCM is the only encoding
-// supported by nsAudioStream.
-#define WAVE_FORMAT_ENCODING_PCM 1
-
-enum State {
-  STATE_LOADING_METADATA,
-  STATE_BUFFERING,
-  STATE_PLAYING,
-  STATE_SEEKING,
-  STATE_PAUSED,
-  STATE_ENDED,
-  STATE_ERROR,
-  STATE_SHUTDOWN
-};
-
-/*
-  A single nsWaveStateMachine instance is owned by the decoder, created
-   on-demand at load time.  Upon creation, the decoder immediately
-   dispatches the state machine event to the decode thread to begin
-   execution.  Once running, metadata loading begins immediately.  If this
-   completes successfully, the state machine will move into a paused state
-   awaiting further commands.  The state machine provides a small set of
-   threadsafe methods enabling the main thread to play, pause, seek, and
-   query parameters.
-
-   An weak (raw) pointer to the decoder's nsMediaStream is used by the state
-   machine to read data, seek, and query stream information.  The decoder is
-   responsible for creating and opening the stream, and may also cancel it.
-   Every other stream operation is performed on the playback thread by the
-   state machine.  A cancel from the main thread will force any in-flight
-   stream operations to abort.
- */
-class nsWaveStateMachine : public nsRunnable
-{
-public:
-  nsWaveStateMachine(nsWaveDecoder* aDecoder,
-                     TimeDuration aBufferWaitTime, double aInitialVolume);
-  ~nsWaveStateMachine();
-
-  void SetStream(nsMediaStream* aStream) { mStream = aStream; }
-
-  // Set specified volume.  aVolume must be in range [0.0, 1.0].
-  // Threadsafe.
-  void SetVolume(double aVolume);
-
-  /*
-    The following four member functions initiate the appropriate state
-    transition suggested by the function name.  Threadsafe.
-   */
-  void Play();
-  void Pause();
-  void Seek(double aTime);
-  void Shutdown();
-
-  // Returns the playback length of the audio data in seconds, calculated
-  // from the length extracted from the metadata.  Returns NaN if called
-  // before metadata validation has completed.  Threadsafe.
-  double GetDuration();
-
-  // Returns the number of channels extracted from the metadata.  Returns 0
-  // if called before metadata validation has completed.  Threadsafe.
-  PRUint32 GetChannels();
-
-  // Returns the audio sample rate (number of samples per second) extracted
-  // from the metadata.  Returns 0 if called before metadata validation has
-  // completed.  Threadsafe.
-  PRUint32 GetSampleRate();
-
-  // Returns true if the state machine is seeking.  Threadsafe.
-  PRBool IsSeeking();
-
-  // Returns true if the state machine has reached the end of playback.  Threadsafe.
-  PRBool IsEnded();
-
-  // Main state machine loop. Runs forever, until shutdown state is reached.
-  NS_IMETHOD Run();
-
-  // Called by the decoder, on the main thread.
-  nsMediaDecoder::Statistics GetStatistics();
-
-  // Called on the decoder thread
-  void NotifyBytesConsumed(PRInt64 aBytes);
-
-  // Called by decoder and main thread.
-  nsHTMLMediaElement::NextFrameStatus GetNextFrameStatus();
-
-  // Clear the flag indicating that a playback position change event is
-  // currently queued and return the current time. This is called from the
-  // main thread.
-  double GetTimeForPositionChange();
-
-  nsresult GetBuffered(nsTimeRanges* aBuffered);
-
-private:
-  // Returns PR_TRUE if we're in shutdown state. Threadsafe.
-  PRBool IsShutdown();
-
-  // Reads from the media stream. Returns PR_FALSE on failure or EOF.  If
-  // aBytesRead is non-null, the number of bytes read will be returned via
-  // this.
-  PRBool ReadAll(char* aBuf, PRInt64 aSize, PRInt64* aBytesRead);
-
-  void UpdateReadyState() {
-    PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mMonitor);
-
-    nsCOMPtr<nsIRunnable> event;
-    switch (GetNextFrameStatus()) {
-      case nsHTMLMediaElement::NEXT_FRAME_UNAVAILABLE_BUFFERING:
-        event = NS_NewRunnableMethod(mDecoder, &nsWaveDecoder::NextFrameUnavailableBuffering);
-        break;
-      case nsHTMLMediaElement::NEXT_FRAME_AVAILABLE:
-        event = NS_NewRunnableMethod(mDecoder, &nsWaveDecoder::NextFrameAvailable);
-        break;
-      case nsHTMLMediaElement::NEXT_FRAME_UNAVAILABLE:
-        event = NS_NewRunnableMethod(mDecoder, &nsWaveDecoder::NextFrameUnavailable);
-        break;
-      default:
-        PR_NOT_REACHED("unhandled frame state");
-    }
-
-    NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
-  }
-
-  // Change the current state and wake the playback thread if it is waiting
-  // on mMonitor.  Used by public member functions called from both threads,
-  // so must hold mMonitor.  Threadsafe.
-  void ChangeState(State aState);
-
-  // Create and initialize audio stream using current audio parameters.
-  void OpenAudioStream(nsAutoMonitor& aMonitor);
-
-  // Shut down and dispose audio stream.
-  void CloseAudioStream();
-
-  // Read RIFF_INITIAL_SIZE from the beginning of the stream and verify that
-  // the stream data is a RIFF bitstream containing WAVE data.
-  PRBool LoadRIFFChunk();
-
-  // Read forward in the stream until aWantedChunk is found.  Return chunk
-  // size in aChunkSize.  aChunkSize will not be rounded up if the chunk
-  // size is odd.
-  PRBool ScanForwardUntil(PRUint32 aWantedChunk, PRUint32* aChunkSize);
-
-  // Scan forward in the stream looking for the WAVE format chunk.  If
-  // found, parse and validate required metadata, then use it to set
-  // mSampleRate, mChannels, mSampleSize, and mSampleFormat.
-  PRBool LoadFormatChunk();
-
-  // Scan forward in the stream looking for the start of the PCM data.  If
-  // found, record the data length and offset in mWaveLength and
-  // mWavePCMOffset.
-  PRBool FindDataOffset();
-
-  // Return the length of the PCM data.
-  PRInt64 GetDataLength();
-
-  // Fire a PlaybackPositionChanged event.  If aCoalesce is true and a
-  // PlaybackPositionChanged event is already pending, an event is not
-  // fired.
-  void FirePositionChanged(PRBool aCoalesce);
-
-  // Returns the number of seconds that aBytes represents based on the
-  // current audio parameters.  e.g.  176400 bytes is 1 second at 16-bit
-  // stereo 44.1kHz.
-  double BytesToTime(PRInt64 aBytes) const
-  {
-    NS_ABORT_IF_FALSE(mMetadataValid, "Requires valid metadata");
-    NS_ABORT_IF_FALSE(aBytes >= 0, "Must be >= 0");
-    return double(aBytes) / mSampleRate / mSampleSize;
-  }
-
-  // Returns the number of bytes that aTime represents based on the current
-  // audio parameters.  e.g.  1 second is 176400 bytes at 16-bit stereo
-  // 44.1kHz.
-  PRInt64 TimeToBytes(double aTime) const
-  {
-    NS_ABORT_IF_FALSE(mMetadataValid, "Requires valid metadata");
-    NS_ABORT_IF_FALSE(aTime >= 0.0f, "Must be >= 0");
-    return RoundDownToSample(PRInt64(aTime * mSampleRate * mSampleSize));
-  }
-
-  // Rounds aBytes down to the nearest complete sample.  Assumes beginning
-  // of byte range is already sample aligned by caller.
-  PRInt64 RoundDownToSample(PRInt64 aBytes) const
-  {
-    NS_ABORT_IF_FALSE(mMetadataValid, "Requires valid metadata");
-    NS_ABORT_IF_FALSE(aBytes >= 0, "Must be >= 0");
-    return aBytes - (aBytes % mSampleSize);
-  }
-
-  // Weak (raw) pointer to our decoder instance.  The decoder manages the
-  // lifetime of the state machine object, so it is guaranteed that the
-  // state machine will not outlive the decoder.  The decoder is not
-  // threadsafe, so this pointer must only be used to create runnable events
-  // targeted at the main thread.
-  nsWaveDecoder* mDecoder;
-
-  // Weak (raw) pointer to a media stream.  The decoder manages the lifetime
-  // of the stream, so it is guaranteed that the stream will live as long as
-  // the state machine.  The stream is threadsafe, but is only used on the
-  // playback thread except for create, open, and cancel, which are called
-  // from the main thread.
-  nsMediaStream* mStream;
-
-  // Our audio stream.  Created on demand when entering playback state.  It
-  // is destroyed when seeking begins and will not be reinitialized until
-  // playback resumes, so it is possible for this to be null.
-  nsRefPtr<nsAudioStream> mAudioStream;
-
-  // Maximum time to spend waiting for data during buffering.
-  TimeDuration mBufferingWait;
-
-  // Machine time that buffering began, used with mBufferingWait to time out
-  // buffering.
-  TimeStamp mBufferingStart;
-
-  // Download position where we should stop buffering.  Only accessed
-  // in the decoder thread.
-  PRInt64 mBufferingEndOffset;
-
-  /*
-    Metadata extracted from the WAVE header.  Used to initialize the audio
-    stream, and for byte<->time domain conversions.
-  */
-
-  // Number of samples per second.  Limited to range [100, 96000] in LoadFormatChunk.
-  PRUint32 mSampleRate;
-
-  // Number of channels.  Limited to range [1, 2] in LoadFormatChunk.
-  PRUint32 mChannels;
-
-  // Size of a single sample segment, which includes a sample for each
-  // channel (interleaved).
-  PRUint32 mSampleSize;
-
-  // The sample format of the PCM data.
-  nsAudioStream::SampleFormat mSampleFormat;
-
-  // Size of PCM data stored in the WAVE as reported by the data chunk in
-  // the media.
-  PRInt64 mWaveLength;
-
-  // Start offset of the PCM data in the media stream.  Extends mWaveLength
-  // bytes.
-  PRInt64 mWavePCMOffset;
-
-  /*
-    All member variables following this comment are accessed by both
-    threads and must be synchronized via mMonitor.
-  */
-  PRMonitor* mMonitor;
-
-  // The state to enter when the state machine loop iterates next.
-  State mState;
-
-  // A queued state transition.  This is used to record the next state
-  // transition when play or pause is requested during seeking or metadata
-  // loading to ensure a completed metadata load or seek returns to the most
-  // recently requested state on completion.
-  State mNextState;
-
-  // Current playback position in the stream.
-  PRInt64 mPlaybackPosition;
-
-  // Volume that the audio backend will be initialized with.
-  double mInitialVolume;
-
-  // Time position (in seconds) to seek to.  Set by Seek(double).
-  double mSeekTime;
-
-  // True once metadata has been parsed and validated. Users of mSampleRate,
-  // mChannels, mSampleSize, mSampleFormat, mWaveLength, mWavePCMOffset must
-  // check this flag before assuming the values are valid.
-  PRPackedBool mMetadataValid;
-
-  // True if an event to notify about a change in the playback position has
-  // been queued, but not yet run.  It is set to false when the event is
-  // run.  This allows coalescing of these events as they can be produced
-  // many times per second.
-  PRPackedBool mPositionChangeQueued;
-
-  // True if paused.  Tracks only the play/paused state.
-  PRPackedBool mPaused;
-
-  // True if playback of the audio stream has finished, and the audio stream
-  // has been drained. This means playback of the file has ended.
-  PRPackedBool mPlaybackEnded;
-};
-
-nsWaveStateMachine::nsWaveStateMachine(nsWaveDecoder* aDecoder,
-                                       TimeDuration aBufferWaitTime,
-                                       double aInitialVolume)
-  : mDecoder(aDecoder),
-    mStream(nsnull),
-    mBufferingWait(aBufferWaitTime),
-    mBufferingStart(),
-    mBufferingEndOffset(0),
-    mSampleRate(0),
-    mChannels(0),
-    mSampleSize(0),
-    mSampleFormat(nsAudioStream::FORMAT_S16_LE),
-    mWaveLength(0),
-    mWavePCMOffset(0),
-    mMonitor(nsnull),
-    mState(STATE_LOADING_METADATA),
-    mNextState(STATE_PAUSED),
-    mPlaybackPosition(0),
-    mInitialVolume(aInitialVolume),
-    mSeekTime(0.0f),
-    mMetadataValid(PR_FALSE),
-    mPositionChangeQueued(PR_FALSE),
-    mPaused(mNextState == STATE_PAUSED),
-    mPlaybackEnded(PR_FALSE)
-{
-  mMonitor = nsAutoMonitor::NewMonitor("nsWaveStateMachine");
-}
-
-nsWaveStateMachine::~nsWaveStateMachine()
-{
-  nsAutoMonitor::DestroyMonitor(mMonitor);
-}
-
-void
-nsWaveStateMachine::Shutdown()
-{
-  ChangeState(STATE_SHUTDOWN);
-}
-
-void
-nsWaveStateMachine::Play()
-{
-  nsAutoMonitor monitor(mMonitor);
-  mPaused = PR_FALSE;
-  mPlaybackEnded = PR_FALSE;
-  if (mState == STATE_ENDED) {
-    Seek(0);
-    return;
-  }
-  if (mState == STATE_LOADING_METADATA || mState == STATE_SEEKING) {
-    mNextState = STATE_PLAYING;
-  } else {
-    ChangeState(STATE_PLAYING);
-  }
-}
-
-void
-nsWaveStateMachine::SetVolume(double aVolume)
-{
-  nsAutoMonitor monitor(mMonitor);
-  mInitialVolume = aVolume;
-  if (mAudioStream) {
-    mAudioStream->SetVolume(aVolume);
-  }
-}
-
-void
-nsWaveStateMachine::Pause()
-{
-  nsAutoMonitor monitor(mMonitor);
-  mPaused = PR_TRUE;
-  if (mState == STATE_LOADING_METADATA || mState == STATE_SEEKING ||
-      mState == STATE_BUFFERING || mState == STATE_ENDED) {
-    mNextState = STATE_PAUSED;
-  } else if (mState == STATE_PLAYING) {
-    ChangeState(STATE_PAUSED);
-  }
-}
 
-void
-nsWaveStateMachine::Seek(double aTime)
-{
-  nsAutoMonitor monitor(mMonitor);
-  mPlaybackEnded = PR_FALSE;
-  mSeekTime = aTime;
-  if (mSeekTime < 0.0f) {
-    mSeekTime = 0.0f;
-  }
-  if (mState == STATE_LOADING_METADATA) {
-    mNextState = STATE_SEEKING;
-  } else if (mState != STATE_SEEKING) {
-    if (mState == STATE_ENDED) {
-      mNextState = mPaused ? STATE_PAUSED : STATE_PLAYING;
-    } else if (mState != STATE_BUFFERING) {
-      mNextState = mState;
-    }
-    ChangeState(STATE_SEEKING);
-  }
-  NS_ASSERTION(IsSeeking(), "IsSeeking() must return true when seeking");
-}
-
-double
-nsWaveStateMachine::GetDuration()
-{
-  nsAutoMonitor monitor(mMonitor);
-  if (mMetadataValid) {
-    return BytesToTime(GetDataLength());
-  }
-  return std::numeric_limits<double>::quiet_NaN();
-}
-
-PRUint32
-nsWaveStateMachine::GetChannels()
-{
-  nsAutoMonitor monitor(mMonitor);
-  if (mMetadataValid) {
-    return mChannels;
-  }
-  return 0;
-}
-
-PRUint32
-nsWaveStateMachine::GetSampleRate()
-{
-  nsAutoMonitor monitor(mMonitor);
-  if (mMetadataValid) {
-    return mSampleRate;
-  }
-  return 0;
-}
-
-PRBool
-nsWaveStateMachine::IsSeeking()
-{
-  nsAutoMonitor monitor(mMonitor);
-  return mState == STATE_SEEKING || mNextState == STATE_SEEKING;
-}
-
-PRBool
-nsWaveStateMachine::IsEnded()
-{
-  nsAutoMonitor monitor(mMonitor);
-  return mPlaybackEnded;
-}
-
-nsHTMLMediaElement::NextFrameStatus
-nsWaveStateMachine::GetNextFrameStatus()
-{
-  nsAutoMonitor monitor(mMonitor);
-  if (mState == STATE_BUFFERING)
-    return nsHTMLMediaElement::NEXT_FRAME_UNAVAILABLE_BUFFERING;
-  // If mMetadataValid is false then we can't call GetDataLength because
-  // we haven't got the length from the Wave header yet. But we know that
-  // if we haven't read the metadata then we don't have playable data.
-  if (mMetadataValid &&
-      mPlaybackPosition < mStream->GetCachedDataEnd(mPlaybackPosition) &&
-      mPlaybackPosition < mWavePCMOffset + GetDataLength())
-    return nsHTMLMediaElement::NEXT_FRAME_AVAILABLE;
-  return nsHTMLMediaElement::NEXT_FRAME_UNAVAILABLE;
-}
-
-double
-nsWaveStateMachine::GetTimeForPositionChange()
-{
-  nsAutoMonitor monitor(mMonitor);
-  mPositionChangeQueued = PR_FALSE;
-  return BytesToTime(mPlaybackPosition - mWavePCMOffset);
-}
-
-NS_IMETHODIMP
-nsWaveStateMachine::Run()
-{
-  // Monitor is held by this thread almost permanently, but must be manually
-  // dropped during long operations to prevent the main thread from blocking
-  // when calling methods on the state machine object.
-  nsAutoMonitor monitor(mMonitor);
-
-  for (;;) {
-    switch (mState) {
-    case STATE_LOADING_METADATA:
-      {
-        monitor.Exit();
-        PRBool loaded = LoadRIFFChunk() && LoadFormatChunk() && FindDataOffset();
-        monitor.Enter();
-
-        if (!loaded) {
-          ChangeState(STATE_ERROR);
-        }
-
-        if (mState == STATE_LOADING_METADATA) {
-          mMetadataValid = PR_TRUE;
-          if (mNextState != STATE_SEEKING) {
-            nsCOMPtr<nsIRunnable> event = NS_NewRunnableMethod(mDecoder, &nsWaveDecoder::MetadataLoaded);
-            NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
-          }
-          ChangeState(mNextState);
-        }
-      }
-      break;
-
-    case STATE_BUFFERING: {
-      TimeStamp now = TimeStamp::Now();
-      if (now - mBufferingStart < mBufferingWait &&
-          mStream->GetCachedDataEnd(mPlaybackPosition) < mBufferingEndOffset &&
-          !mStream->IsDataCachedToEndOfStream(mPlaybackPosition) &&
-          !mStream->IsSuspendedByCache()) {
-        LOG(PR_LOG_DEBUG,
-            ("In buffering: buffering data until %d bytes available or %f seconds\n",
-             PRUint32(mBufferingEndOffset - mStream->GetCachedDataEnd(mPlaybackPosition)),
-             (mBufferingWait - (now - mBufferingStart)).ToSeconds()));
-        monitor.Wait(PR_MillisecondsToInterval(1000));
-      } else {
-        ChangeState(mNextState);
-        UpdateReadyState();
-      }
-
-      break;
-    }
-
-    case STATE_PLAYING: {
-      if (!mAudioStream) {
-        OpenAudioStream(monitor);
-        if (!mAudioStream) {
-          ChangeState(STATE_ERROR);
-          break;
-        }
-      }
-
-      TimeStamp now = TimeStamp::Now();
-      TimeStamp lastWakeup = now -
-        TimeDuration::FromMilliseconds(AUDIO_BUFFER_LENGTH);
-
-      do {
-        TimeDuration sleepTime = now - lastWakeup;
-        lastWakeup = now;
-
-        // We aim to have AUDIO_BUFFER_LENGTH milliseconds of audio
-        // buffered, but only sleep for AUDIO_BUFFER_WAKEUP milliseconds
-        // (waking early to refill before the backend underruns).  Since we
-        // wake early, we only buffer sleepTime milliseconds of audio since
-        // there is still AUDIO_BUFFER_LENGTH - sleepTime milliseconds of
-        // audio buffered.
-        TimeDuration targetTime =
-          TimeDuration::FromMilliseconds(AUDIO_BUFFER_LENGTH);
-        if (sleepTime < targetTime) {
-          targetTime = sleepTime;
-        }
-
-        PRInt64 len = TimeToBytes(double(targetTime.ToSeconds()));
-
-        PRInt64 leftToPlay =
-          GetDataLength() - (mPlaybackPosition - mWavePCMOffset);
-        if (leftToPlay <= len) {
-          len = leftToPlay;
-          ChangeState(STATE_ENDED);
-        }
-
-        PRInt64 availableOffset = mStream->GetCachedDataEnd(mPlaybackPosition);
-
-        // Don't buffer if we're at the end of the stream, or if the
-        // load has been suspended by the cache (in the latter case
-        // we need to advance playback to free up cache space).
-        if (mState != STATE_ENDED &&
-            availableOffset < mPlaybackPosition + len &&
-            !mStream->IsSuspendedByCache()) {
-          mBufferingStart = now;
-          mBufferingEndOffset = mPlaybackPosition +
-            TimeToBytes(double(mBufferingWait.ToSeconds()));
-          mBufferingEndOffset = PR_MAX(mPlaybackPosition + len,
-                                       mBufferingEndOffset);
-          mNextState = mState;
-          ChangeState(STATE_BUFFERING);
-
-          UpdateReadyState();
-          break;
-        }
-
-        if (len > 0) {
-          nsAutoArrayPtr<char> buf(new char[size_t(len)]);
-          PRInt64 got = 0;
-
-          monitor.Exit();
-          PRBool ok = ReadAll(buf.get(), len, &got);
-          monitor.Enter();
-
-          // Reached EOF.
-          if (!ok) {
-            ChangeState(STATE_ENDED);
-            if (got == 0) {
-              break;
-            }
-          }
-
-          // Calculate difference between the current media stream position
-          // and the expected end of the PCM data.
-          PRInt64 endDelta = mWavePCMOffset + mWaveLength - mPlaybackPosition;
-          if (endDelta < 0) {
-            // Read past the end of PCM data.  Adjust got to avoid playing
-            // back trailing data.
-            got -= -endDelta;
-            ChangeState(STATE_ENDED);
-          }
-
-          if (mState == STATE_ENDED) {
-            got = RoundDownToSample(got);
-          }
-
-          PRUint32 sampleSize = mSampleFormat == nsAudioStream::FORMAT_U8 ? 1 : 2;
-          NS_ABORT_IF_FALSE(got % sampleSize == 0, "Must write complete samples");
-          PRUint32 lengthInSamples = PRUint32(got / sampleSize);
-
-          monitor.Exit();
-          mAudioStream->Write(buf.get(), lengthInSamples, PR_FALSE);
-          monitor.Enter();
-
-          FirePositionChanged(PR_FALSE);
-        }
-
-        if (mState == STATE_PLAYING) {
-          monitor.Wait(PR_MillisecondsToInterval(AUDIO_BUFFER_WAKEUP));
-          now = TimeStamp::Now();
-        }
-      } while (mState == STATE_PLAYING);
-      break;
-    }
-
-    case STATE_SEEKING:
-      {
-        CloseAudioStream();
-
-        mSeekTime = NS_MIN(mSeekTime, GetDuration());
-        double seekTime = mSeekTime;
-
-        // Calculate relative offset within PCM data.
-        PRInt64 position = RoundDownToSample(TimeToBytes(seekTime));
-        NS_ABORT_IF_FALSE(position >= 0 && position <= GetDataLength(),
-                          "Invalid seek position");
-        // Convert to absolute offset within stream.
-        position += mWavePCMOffset;
-
-        // If in the midst of a seek, report the requested seek time
-        // as the current time as required by step 8 of 4.8.10.9 'Seeking'
-        // in the WHATWG spec.
-        PRInt64 oldPosition = mPlaybackPosition;
-        mPlaybackPosition = position;
-        FirePositionChanged(PR_TRUE);
-
-        monitor.Exit();
-        nsCOMPtr<nsIRunnable> startEvent =
-          NS_NewRunnableMethod(mDecoder, &nsWaveDecoder::SeekingStarted);
-        NS_DispatchToMainThread(startEvent, NS_DISPATCH_SYNC);
-        monitor.Enter();
-
-        if (mState == STATE_SHUTDOWN) {
-          break;
-        }
-
-        monitor.Exit();
-        nsresult rv;
-        rv = mStream->Seek(nsISeekableStream::NS_SEEK_SET, position);
-        monitor.Enter();
-        if (NS_FAILED(rv)) {
-          NS_WARNING("Seek failed");
-          mPlaybackPosition = oldPosition;
-          FirePositionChanged(PR_TRUE);
-        }
-
-        if (mState == STATE_SHUTDOWN) {
-          break;
-        }
-
-        if (mState == STATE_SEEKING && mSeekTime == seekTime) {
-          // Special case #1: if a seek was requested during metadata load,
-          // mNextState will have been clobbered.  This can only happen when
-          // we're instantiating a decoder to service a seek request after
-          // playback has ended, so we know that the clobbered mNextState
-          // was PAUSED.
-          // Special case #2: if a seek is requested after the state machine
-          // entered STATE_ENDED but before the user has seen the ended
-          // event, playback has not ended as far as the user's
-          // concerned--the state machine needs to return to the last
-          // playback state.
-          // Special case #3: if seeking to the end of the media, transition
-          // directly into STATE_ENDED.
-          State nextState = mNextState;
-          if (nextState == STATE_SEEKING) {
-            nextState = STATE_PAUSED;
-          } else if (nextState == STATE_ENDED) {
-            nextState = mPaused ? STATE_PAUSED : STATE_PLAYING;
-          } else if (GetDuration() == seekTime) {
-            nextState = STATE_ENDED;
-          }
-          ChangeState(nextState);
-        }
-
-        if (mState != STATE_SEEKING) {
-          monitor.Exit();
-          nsCOMPtr<nsIRunnable> stopEvent =
-            NS_NewRunnableMethod(mDecoder, &nsWaveDecoder::SeekingStopped);
-          NS_DispatchToMainThread(stopEvent, NS_DISPATCH_SYNC);
-          monitor.Enter();
-        }
-      }
-      break;
-
-    case STATE_PAUSED:
-      monitor.Wait();
-      break;
-
-    case STATE_ENDED:
-      FirePositionChanged(PR_TRUE);
-
-      if (mAudioStream) {
-        monitor.Exit();
-        mAudioStream->Drain();
-        monitor.Enter();
-
-        // After the drain call the audio stream is unusable. Close it so that
-        // next time audio is used a new stream is created.
-        CloseAudioStream();
-      }
-
-      mPlaybackEnded = PR_TRUE;
-
-      if (mState == STATE_ENDED) {
-        nsCOMPtr<nsIRunnable> event =
-          NS_NewRunnableMethod(mDecoder, &nsWaveDecoder::PlaybackEnded);
-        NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
-
-        // We've finished playback. Shutdown the state machine thread, 
-        // in order to save memory on thread stacks, particuarly on Linux.
-        event = new ShutdownThreadEvent(mDecoder->mPlaybackThread);
-        NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
-        mDecoder->mPlaybackThread = nsnull;
-        return NS_OK;
-      }
-      break;
-
-    case STATE_ERROR:
-      {
-        nsCOMPtr<nsIRunnable> event = NS_NewRunnableMethod(mDecoder, &nsWaveDecoder::DecodeError);
-        NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
-
-        monitor.Wait();
-
-        if (mState != STATE_SHUTDOWN) {
-          NS_WARNING("Invalid state transition");
-          ChangeState(STATE_ERROR);
-        }
-      }
-      break;
-
-    case STATE_SHUTDOWN:
-      mPlaybackEnded = PR_TRUE;
-      CloseAudioStream();
-      return NS_OK;
-    }
-  }
-
-  return NS_OK;
-}
-
-#if defined(DEBUG)
-static PRBool
-IsValidStateTransition(State aStartState, State aEndState)
-{
-  if (aEndState == STATE_SHUTDOWN) {
-    return PR_TRUE;
-  }
-
-  if (aStartState == aEndState) {
-    LOG(PR_LOG_WARNING, ("Transition to current state requested"));
-    return PR_TRUE;
-  }
-
-  switch (aStartState) {
-  case STATE_LOADING_METADATA:
-    if (aEndState == STATE_PLAYING || aEndState == STATE_SEEKING ||
-        aEndState == STATE_PAUSED || aEndState == STATE_ERROR)
-      return PR_TRUE;
-    break;
-  case STATE_BUFFERING:
-    if (aEndState == STATE_PLAYING || aEndState == STATE_PAUSED ||
-        aEndState == STATE_SEEKING)
-      return PR_TRUE;
-    break;
-  case STATE_PLAYING:
-    if (aEndState == STATE_BUFFERING || aEndState == STATE_SEEKING ||
-        aEndState == STATE_ENDED || aEndState == STATE_PAUSED)
-      return PR_TRUE;
-    break;
-  case STATE_SEEKING:
-    if (aEndState == STATE_PLAYING || aEndState == STATE_PAUSED ||
-        aEndState == STATE_ENDED)
-      return PR_TRUE;
-    break;
-  case STATE_PAUSED:
-    if (aEndState == STATE_PLAYING || aEndState == STATE_SEEKING)
-      return PR_TRUE;
-    break;
-  case STATE_ENDED:
-    if (aEndState == STATE_SEEKING)
-      return PR_TRUE;
-    /* fallthrough */
-  case STATE_ERROR:
-  case STATE_SHUTDOWN:
-    break;
-  }
-
-  LOG(PR_LOG_ERROR, ("Invalid state transition from %d to %d", aStartState, aEndState));
-  return PR_FALSE;
-}
-#endif
-
-void
-nsWaveStateMachine::ChangeState(State aState)
+nsDecoderStateMachine* nsWaveDecoder::CreateStateMachine()
 {
-  nsAutoMonitor monitor(mMonitor);
-  if (mState == STATE_SHUTDOWN) {
-    LOG(PR_LOG_WARNING, ("In shutdown, state transition ignored"));
-    return;
-  }
-#if defined(DEBUG)
-  NS_ABORT_IF_FALSE(IsValidStateTransition(mState, aState), "Invalid state transition");
-#endif
-  mState = aState;
-  monitor.NotifyAll();
-}
-
-void
-nsWaveStateMachine::OpenAudioStream(nsAutoMonitor& aMonitor)
-{
-  NS_ABORT_IF_FALSE(mMetadataValid,
-                    "Attempting to initialize audio stream with invalid metadata");
-
-  nsRefPtr<nsAudioStream> audioStream = nsAudioStream::AllocateStream();
-  if (!audioStream) {
-    LOG(PR_LOG_ERROR, ("Could not create audio stream"));
-    return;
-  }
-
-  // Drop the monitor while initializing the stream because remote
-  // audio streams wait on a synchronous event running on the main
-  // thread, and holding the decoder monitor while waiting for this
-  // can result in deadlocks.
-  aMonitor.Exit();
-  audioStream->Init(mChannels, mSampleRate, mSampleFormat);
-  aMonitor.Enter();
-
-  mAudioStream = audioStream;
-  mAudioStream->SetVolume(mInitialVolume);
-}
-
-void
-nsWaveStateMachine::CloseAudioStream()
-{
-  if (mAudioStream) {
-    mAudioStream->Shutdown();
-    mAudioStream = nsnull;
-  }
-}
-
-nsMediaDecoder::Statistics
-nsWaveStateMachine::GetStatistics()
-{
-  nsMediaDecoder::Statistics result;
-  nsAutoMonitor monitor(mMonitor);
-  result.mDownloadRate = mStream->GetDownloadRate(&result.mDownloadRateReliable);
-  result.mPlaybackRate = mSampleRate*mChannels*mSampleSize;
-  result.mPlaybackRateReliable = PR_TRUE;
-  result.mTotalBytes = mStream->GetLength();
-  result.mDownloadPosition = mStream->GetCachedDataEnd(mPlaybackPosition);
-  result.mDecoderPosition = mPlaybackPosition;
-  result.mPlaybackPosition = mPlaybackPosition;
-  return result;
-}
-
-void
-nsWaveStateMachine::NotifyBytesConsumed(PRInt64 aBytes)
-{
-  nsAutoMonitor monitor(mMonitor);
-  mPlaybackPosition += aBytes;
-}
-
-static PRUint32
-ReadUint32BE(const char** aBuffer)
-{
-  PRUint32 result =
-    PRUint8((*aBuffer)[0]) << 24 |
-    PRUint8((*aBuffer)[1]) << 16 |
-    PRUint8((*aBuffer)[2]) << 8 |
-    PRUint8((*aBuffer)[3]);
-  *aBuffer += sizeof(PRUint32);
-  return result;
-}
-
-static PRUint32
-ReadUint32LE(const char** aBuffer)
-{
-  PRUint32 result =
-    PRUint8((*aBuffer)[3]) << 24 |
-    PRUint8((*aBuffer)[2]) << 16 |
-    PRUint8((*aBuffer)[1]) << 8 |
-    PRUint8((*aBuffer)[0]);
-  *aBuffer += sizeof(PRUint32);
-  return result;
-}
-
-static PRUint16
-ReadUint16LE(const char** aBuffer)
-{
-  PRUint16 result =
-    PRUint8((*aBuffer)[1]) << 8 |
-    PRUint8((*aBuffer)[0]) << 0;
-  *aBuffer += sizeof(PRUint16);
-  return result;
-}
-
-PRBool
-nsWaveStateMachine::IsShutdown()
-{
-  nsAutoMonitor monitor(mMonitor);
-  return mState == STATE_SHUTDOWN;
-}
-
-PRBool
-nsWaveStateMachine::ReadAll(char* aBuf, PRInt64 aSize, PRInt64* aBytesRead = nsnull)
-{
-  PRUint32 got = 0;
-  if (aBytesRead) {
-    *aBytesRead = 0;
-  }
-  do {
-    PRUint32 read = 0;
-    if (NS_FAILED(mStream->Read(aBuf + got, PRUint32(aSize - got), &read))) {
-      NS_WARNING("Stream read failed");
-      return PR_FALSE;
-    }
-    if (IsShutdown() || read == 0) {
-      return PR_FALSE;
-    }
-    NotifyBytesConsumed(read);
-    got += read;
-    if (aBytesRead) {
-      *aBytesRead = got;
-    }
-  } while (got != aSize);
-  return PR_TRUE;
-}
-
-PRBool
-nsWaveStateMachine::LoadRIFFChunk()
-{
-  char riffHeader[RIFF_INITIAL_SIZE];
-  const char* p = riffHeader;
-
-  NS_ABORT_IF_FALSE(mStream->Tell() == 0,
-                    "LoadRIFFChunk called when stream in invalid state");
-
-  if (!ReadAll(riffHeader, sizeof(riffHeader))) {
-    return PR_FALSE;
-  }
-
-  if (ReadUint32BE(&p) != RIFF_CHUNK_MAGIC) {
-    NS_WARNING("Stream data not in RIFF format");
-    return PR_FALSE;
-  }
-
-  // Skip over RIFF size field.
-  p += 4;
-
-  if (ReadUint32BE(&p) != WAVE_CHUNK_MAGIC) {
-    NS_WARNING("Expected WAVE chunk");
-    return PR_FALSE;
-  }
-
-  return PR_TRUE;
-}
-
-PRBool
-nsWaveStateMachine::ScanForwardUntil(PRUint32 aWantedChunk, PRUint32* aChunkSize)
-{
-  NS_ABORT_IF_FALSE(aChunkSize, "Require aChunkSize argument");
-  *aChunkSize = 0;
-
-  for (;;) {
-    char chunkHeader[8];
-    const char* p = chunkHeader;
-
-    if (!ReadAll(chunkHeader, sizeof(chunkHeader))) {
-      return PR_FALSE;
-    }
-
-    PRUint32 magic = ReadUint32BE(&p);
-    PRUint32 chunkSize = ReadUint32LE(&p);
-
-    if (magic == aWantedChunk) {
-      *aChunkSize = chunkSize;
-      return PR_TRUE;
-    }
-
-    // RIFF chunks are two-byte aligned, so round up if necessary.
-    chunkSize += chunkSize % 2;
-
-    while (chunkSize > 0) {
-      PRUint32 size = PR_MIN(chunkSize, 1 << 16);
-      nsAutoArrayPtr<char> chunk(new char[size]);
-      if (!ReadAll(chunk.get(), size)) {
-        return PR_FALSE;
-      }
-      chunkSize -= size;
-    }
-  }
-}
-
-PRBool
-nsWaveStateMachine::LoadFormatChunk()
-{
-  PRUint32 fmtSize, rate, channels, sampleSize, sampleFormat;
-  char waveFormat[WAVE_FORMAT_CHUNK_SIZE];
-  const char* p = waveFormat;
-
-  // RIFF chunks are always word (two byte) aligned.
-  NS_ABORT_IF_FALSE(mStream->Tell() % 2 == 0,
-                    "LoadFormatChunk called with unaligned stream");
-
-  // The "format" chunk may not directly follow the "riff" chunk, so skip
-  // over any intermediate chunks.
-  if (!ScanForwardUntil(FRMT_CHUNK_MAGIC, &fmtSize)) {
-      return PR_FALSE;
-  }
-
-  if (!ReadAll(waveFormat, sizeof(waveFormat))) {
-    return PR_FALSE;
-  }
-
-  if (ReadUint16LE(&p) != WAVE_FORMAT_ENCODING_PCM) {
-    NS_WARNING("WAVE is not uncompressed PCM, compressed encodings are not supported");
-    return PR_FALSE;
-  }
-
-  channels = ReadUint16LE(&p);
-  rate = ReadUint32LE(&p);
-
-  // Skip over average bytes per second field.
-  p += 4;
-
-  sampleSize = ReadUint16LE(&p);
-
-  sampleFormat = ReadUint16LE(&p);
-
-  // PCM encoded WAVEs are not expected to have an extended "format" chunk,
-  // but I have found WAVEs that have a extended "format" chunk with an
-  // extension size of 0 bytes.  Be polite and handle this rather than
-  // considering the file invalid.  This code skips any extension of the
-  // "format" chunk.
-  if (fmtSize > WAVE_FORMAT_CHUNK_SIZE) {
-    char extLength[2];
-    const char* p = extLength;
-
-    if (!ReadAll(extLength, sizeof(extLength))) {
-      return PR_FALSE;
-    }
-
-    PRUint16 extra = ReadUint16LE(&p);
-    if (fmtSize - (WAVE_FORMAT_CHUNK_SIZE + 2) != extra) {
-      NS_WARNING("Invalid extended format chunk size");
-      return PR_FALSE;
-    }
-    extra += extra % 2;
-
-    if (extra > 0) {
-      nsAutoArrayPtr<char> chunkExtension(new char[extra]);
-      if (!ReadAll(chunkExtension.get(), extra)) {
-        return PR_FALSE;
-      }
-    }
-  }
-
-  // RIFF chunks are always word (two byte) aligned.
-  NS_ABORT_IF_FALSE(mStream->Tell() % 2 == 0,
-                    "LoadFormatChunk left stream unaligned");
-
-  // Make sure metadata is fairly sane.  The rate check is fairly arbitrary,
-  // but the channels check is intentionally limited to mono or stereo
-  // because that's what the audio backend currently supports.
-  if (rate < 100 || rate > 96000 ||
-      channels < 1 || channels > 2 ||
-      (sampleSize != 1 && sampleSize != 2 && sampleSize != 4) ||
-      (sampleFormat != 8 && sampleFormat != 16)) {
-    NS_WARNING("Invalid WAVE metadata");
-    return PR_FALSE;
-  }
-
-  nsAutoMonitor monitor(mMonitor);
-  mSampleRate = rate;
-  mChannels = channels;
-  mSampleSize = sampleSize;
-  if (sampleFormat == 8) {
-    mSampleFormat = nsAudioStream::FORMAT_U8;
-  } else {
-    mSampleFormat = nsAudioStream::FORMAT_S16_LE;
-  }
-  return PR_TRUE;
-}
-
-PRBool
-nsWaveStateMachine::FindDataOffset()
-{
-  // RIFF chunks are always word (two byte) aligned.
-  NS_ABORT_IF_FALSE(mStream->Tell() % 2 == 0,
-                    "FindDataOffset called with unaligned stream");
-
-  // The "data" chunk may not directly follow the "format" chunk, so skip
-  // over any intermediate chunks.
-  PRUint32 length;
-  if (!ScanForwardUntil(DATA_CHUNK_MAGIC, &length)) {
-    return PR_FALSE;
-  }
-
-  PRInt64 offset = mStream->Tell();
-  if (offset <= 0 || offset > PR_UINT32_MAX) {
-    NS_WARNING("PCM data offset out of range");
-    return PR_FALSE;
-  }
-
-  nsAutoMonitor monitor(mMonitor);
-  mWaveLength = length;
-  mWavePCMOffset = PRUint32(offset);
-  return PR_TRUE;
-}
-
-PRInt64
-nsWaveStateMachine::GetDataLength()
-{
-  NS_ABORT_IF_FALSE(mMetadataValid,
-                    "Attempting to initialize audio stream with invalid metadata");
-
-  PRInt64 length = mWaveLength;
-  // If the decoder has a valid content length, and it's shorter than the
-  // expected length of the PCM data, calculate the playback duration from
-  // the content length rather than the expected PCM data length.
-  PRInt64 streamLength = mStream->GetLength();
-  if (streamLength >= 0) {
-    PRInt64 dataLength = PR_MAX(0, streamLength - mWavePCMOffset);
-    length = PR_MIN(dataLength, length);
-  }
-  return length;
-}
-
-void
-nsWaveStateMachine::FirePositionChanged(PRBool aCoalesce)
-{
-  if (aCoalesce && mPositionChangeQueued) {
-    return;
-  }
-
-  mPositionChangeQueued = PR_TRUE;
-  nsCOMPtr<nsIRunnable> event = NS_NewRunnableMethod(mDecoder, &nsWaveDecoder::PlaybackPositionChanged);
-  NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
-}
-
-nsresult
-nsWaveStateMachine::GetBuffered(nsTimeRanges* aBuffered)
-{
-  PRInt64 startOffset = mStream->GetNextCachedData(mWavePCMOffset);
-  while (startOffset >= 0) {
-    PRInt64 endOffset = mStream->GetCachedDataEnd(startOffset);
-    // Bytes [startOffset..endOffset] are cached.
-    aBuffered->Add(BytesToTime(startOffset - mWavePCMOffset),
-                   BytesToTime(endOffset - mWavePCMOffset));
-    startOffset = mStream->GetNextCachedData(endOffset);
-  }
-  return NS_OK;
-}
-
-NS_IMPL_THREADSAFE_ISUPPORTS1(nsWaveDecoder, nsIObserver)
-
-nsWaveDecoder::nsWaveDecoder()
-  : mInitialVolume(1.0f),
-    mCurrentTime(0.0f),
-    mEndedDuration(std::numeric_limits<double>::quiet_NaN()),
-    mEnded(PR_FALSE),
-    mSeekable(PR_TRUE),
-    mResourceLoaded(PR_FALSE),
-    mMetadataLoadedReported(PR_FALSE),
-    mResourceLoadedReported(PR_FALSE)
-{
-  MOZ_COUNT_CTOR(nsWaveDecoder);
-
-#ifdef PR_LOGGING
-  if (!gWaveDecoderLog) {
-    gWaveDecoderLog = PR_NewLogModule("nsWaveDecoder");
-  }
-#endif
-}
-
-nsWaveDecoder::~nsWaveDecoder()
-{
-  MOZ_COUNT_DTOR(nsWaveDecoder);
-  UnpinForSeek();
-}
-
-PRBool
-nsWaveDecoder::Init(nsHTMLMediaElement* aElement)
-{
-  nsMediaDecoder::Init(aElement);
-
-  nsContentUtils::RegisterShutdownObserver(this);
-
-  mPlaybackStateMachine = new nsWaveStateMachine(this,
-    TimeDuration::FromMilliseconds(BUFFERING_TIMEOUT),
-    mInitialVolume);
-  NS_ENSURE_TRUE(mPlaybackStateMachine, PR_FALSE);
-
-  return PR_TRUE;
-}
-
-nsMediaStream*
-nsWaveDecoder::GetCurrentStream()
-{
-  return mStream;
-}
-
-already_AddRefed<nsIPrincipal>
-nsWaveDecoder::GetCurrentPrincipal()
-{
-  if (!mStream) {
-    return nsnull;
-  }
-  return mStream->GetCurrentPrincipal();
-}
-
-double
-nsWaveDecoder::GetCurrentTime()
-{
-  return mCurrentTime;
+  return new nsBuiltinDecoderStateMachine(this, new nsWaveReader(this));
 }
-
-nsresult
-nsWaveDecoder::StartStateMachineThread()
-{
-  NS_ASSERTION(mPlaybackStateMachine, "Must have state machine");
-  if (mPlaybackThread) {
-    return NS_OK;
-  }
-  nsresult rv = NS_NewThread(getter_AddRefs(mPlaybackThread));
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  return mPlaybackThread->Dispatch(mPlaybackStateMachine, NS_DISPATCH_NORMAL);
-}
-
-nsresult
-nsWaveDecoder::Seek(double aTime)
-{
-  if (mPlaybackStateMachine) {
-    mEnded = PR_FALSE;
-    mCurrentTime = aTime;
-    PinForSeek();
-    mPlaybackStateMachine->Seek(aTime);
-    return StartStateMachineThread();
-  }
-
-  return NS_ERROR_FAILURE;
-}
-
-nsresult
-nsWaveDecoder::PlaybackRateChanged()
-{
-  return NS_ERROR_NOT_IMPLEMENTED;
-}
-
-double
-nsWaveDecoder::GetDuration()
-{
-  if (mPlaybackStateMachine) {
-    return mPlaybackStateMachine->GetDuration();
-  }
-  return mEndedDuration;
-}
-
-void
-nsWaveDecoder::Pause()
-{
-  if (mPlaybackStateMachine) {
-    mPlaybackStateMachine->Pause();
-  }
-}
-
-void
-nsWaveDecoder::SetVolume(double aVolume)
-{
-  mInitialVolume = aVolume;
-  if (mPlaybackStateMachine) {
-    mPlaybackStateMachine->SetVolume(aVolume);
-  }
-}
-
-nsresult
-nsWaveDecoder::Play()
-{
-  if (mPlaybackStateMachine) {
-    mEnded = PR_FALSE;
-    mPlaybackStateMachine->Play();
-    return StartStateMachineThread();
-  }
-
-  return NS_ERROR_FAILURE;
-}
-
-void
-nsWaveDecoder::Stop()
-{
-  if (mPlaybackStateMachine) {
-    mPlaybackStateMachine->Shutdown();
-  }
-
-  if (mStream) {
-    mStream->Close();
-  }
-
-  if (mPlaybackThread) {
-    mPlaybackThread->Shutdown();
-  }
-
-  if (mPlaybackStateMachine) {
-    mEndedDuration = mPlaybackStateMachine->GetDuration();
-    mEnded = mPlaybackStateMachine->IsEnded();
-  }
-
-  mPlaybackThread = nsnull;
-  mPlaybackStateMachine = nsnull;
-  mStream = nsnull;
-
-  nsContentUtils::UnregisterShutdownObserver(this);
-}
-
-nsresult
-nsWaveDecoder::Load(nsMediaStream* aStream, nsIStreamListener** aStreamListener,
-                    nsMediaDecoder* aCloneDonor)
-{
-  NS_ASSERTION(aStream, "A stream should be provided");
-
-  if (aStreamListener) {
-    *aStreamListener = nsnull;
-  }
-
-  mStream = aStream;
-
-  nsresult rv = mStream->Open(aStreamListener);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  mPlaybackStateMachine->SetStream(mStream);
-
-  rv = NS_NewThread(getter_AddRefs(mPlaybackThread));
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  rv = mPlaybackThread->Dispatch(mPlaybackStateMachine, NS_DISPATCH_NORMAL);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  return NS_OK;
-}
-
-void
-nsWaveDecoder::MetadataLoaded()
-{
-  if (mShuttingDown) {
-    return;
-  }
-
-  if (mElement) {
-    mElement->MetadataLoaded(mPlaybackStateMachine->GetChannels(),
-                             mPlaybackStateMachine->GetSampleRate());
-    mElement->FirstFrameLoaded(mResourceLoaded);
-  }
-
-  mMetadataLoadedReported = PR_TRUE;
-
-  if (mResourceLoaded) {
-    ResourceLoaded();
-  } else {
-    StartProgress();
-  }
-}
-
-void
-nsWaveDecoder::PlaybackEnded()
-{
-  if (mShuttingDown) {
-    return;
-  }
-
-  if (!mPlaybackStateMachine->IsEnded()) {
-    return;
-  }
-  mEnded = PR_TRUE;
-
-  // Update ready state; now that we've finished playback, we should
-  // switch to HAVE_CURRENT_DATA.
-  UpdateReadyStateForData();
-  if (mElement) {
-    mElement->PlaybackEnded();
-  }
-}
-
-void
-nsWaveDecoder::ResourceLoaded()
-{
-  if (mShuttingDown) {
-    return;
-  }
-
-  mResourceLoaded = PR_TRUE;
-
-  if (!mMetadataLoadedReported || mResourceLoadedReported)
-    return;
-
-  StopProgress();
-
-  if (mElement) {
-    // Ensure the final progress event gets fired
-    mElement->ResourceLoaded();
-  }
-
-  mResourceLoadedReported = PR_TRUE;
-}
-
-void
-nsWaveDecoder::NetworkError()
-{
-  if (mShuttingDown) {
-    return;
-  }
-  if (mElement) {
-    mElement->NetworkError();
-  }
-  Shutdown();
-}
-
-PRBool
-nsWaveDecoder::IsSeeking() const
-{
-  if (mPlaybackStateMachine) {
-    return mPlaybackStateMachine->IsSeeking();
-  }
-  return PR_FALSE;
-}
-
-PRBool
-nsWaveDecoder::IsEnded() const
-{
-  return mEnded;
-}
-
-nsMediaDecoder::Statistics
-nsWaveDecoder::GetStatistics()
-{
-  if (!mPlaybackStateMachine)
-    return Statistics();
-  return mPlaybackStateMachine->GetStatistics();
-}
-
-void
-nsWaveDecoder::NotifySuspendedStatusChanged()
-{
-  if (mStream->IsSuspendedByCache() && mElement) {
-    // if this is an autoplay element, we need to kick off its autoplaying
-    // now so we consume data and hopefully free up cache space
-    mElement->NotifyAutoplayDataReady();
-  }
-}
-
-void
-nsWaveDecoder::NotifyBytesDownloaded()
-{
-  UpdateReadyStateForData();
-  Progress(PR_FALSE);
-}
-
-void
-nsWaveDecoder::NotifyDownloadEnded(nsresult aStatus)
-{
-  if (NS_SUCCEEDED(aStatus)) {
-    ResourceLoaded();
-  } else if (aStatus == NS_BINDING_ABORTED) {
-    // Download has been cancelled by user.
-    if (mElement) {
-      mElement->LoadAborted();
-    }
-  } else if (aStatus != NS_BASE_STREAM_CLOSED) {
-    NetworkError();
-  }
-  UpdateReadyStateForData();
-}
-
-void
-nsWaveDecoder::Shutdown()
-{
-  if (mShuttingDown)
-    return;
-
-  mShuttingDown = PR_TRUE;
-
-  nsMediaDecoder::Shutdown();
-
-  // An event that gets posted to the main thread, when the media element is
-  // being destroyed, to destroy the decoder. Since the decoder shutdown can
-  // block and post events this cannot be done inside destructor calls. So
-  // this event is posted asynchronously to the main thread to perform the
-  // shutdown.
-  nsCOMPtr<nsIRunnable> event =
-    NS_NewRunnableMethod(this, &nsWaveDecoder::Stop);
-  NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
-}
-
-nsresult
-nsWaveDecoder::Observe(nsISupports* aSubject, const char* aTopic, const PRUnichar* aData)
-{
-  if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
-    Shutdown();
-  }
-  return NS_OK;
-}
-
-void
-nsWaveDecoder::NextFrameUnavailableBuffering()
-{
-  NS_ASSERTION(NS_IsMainThread(), "Should be called on main thread");
-  if (!mElement || mShuttingDown || !mPlaybackStateMachine)
-    return;
-
-  mElement->UpdateReadyStateForData(nsHTMLMediaElement::NEXT_FRAME_UNAVAILABLE_BUFFERING);
-}
-
-void
-nsWaveDecoder::NextFrameAvailable()
-{
-  NS_ASSERTION(NS_IsMainThread(), "Should be called on main thread");
-  if (!mElement || mShuttingDown || !mPlaybackStateMachine)
-    return;
-
-  if (!mMetadataLoadedReported) {
-    mElement->UpdateReadyStateForData(nsHTMLMediaElement::NEXT_FRAME_UNAVAILABLE);
-  } else {
-    mElement->UpdateReadyStateForData(nsHTMLMediaElement::NEXT_FRAME_AVAILABLE);
-  }
-}
-
-void
-nsWaveDecoder::NextFrameUnavailable()
-{
-  NS_ASSERTION(NS_IsMainThread(), "Should be called on main thread");
-  if (!mElement || mShuttingDown || !mPlaybackStateMachine)
-    return;
-
-  mElement->UpdateReadyStateForData(nsHTMLMediaElement::NEXT_FRAME_UNAVAILABLE);
-}
-
-void
-nsWaveDecoder::UpdateReadyStateForData()
-{
-  NS_ASSERTION(NS_IsMainThread(), "Should be called on main thread");
-  if (!mElement || mShuttingDown || !mPlaybackStateMachine)
-    return;
-
-  nsHTMLMediaElement::NextFrameStatus frameStatus =
-    mPlaybackStateMachine->GetNextFrameStatus();
-  if (frameStatus == nsHTMLMediaElement::NEXT_FRAME_AVAILABLE &&
-      !mMetadataLoadedReported) {
-    frameStatus = nsHTMLMediaElement::NEXT_FRAME_UNAVAILABLE;
-  }
-  mElement->UpdateReadyStateForData(frameStatus);
-}
-
-void
-nsWaveDecoder::SeekingStarted()
-{
-  if (mShuttingDown) {
-    return;
-  }
-
-  if (mElement) {
-    UpdateReadyStateForData();
-    mElement->SeekStarted();
-  }
-}
-
-void
-nsWaveDecoder::SeekingStopped()
-{
-  UnpinForSeek();
-  if (mShuttingDown) {
-    return;
-  }
-
-  if (mElement) {
-    UpdateReadyStateForData();
-    mElement->SeekCompleted();
-  }
-}
-
-void
-nsWaveDecoder::DecodeError()
-{
-  if (mShuttingDown) {
-    return;
-  }
-  if (mElement) {
-    mElement->DecodeError();
-  }
-  Shutdown();
-}
-
-void
-nsWaveDecoder::PlaybackPositionChanged()
-{
-  if (mShuttingDown) {
-    return;
-  }
-
-  double lastTime = mCurrentTime;
-
-  if (mPlaybackStateMachine) {
-    mCurrentTime = mPlaybackStateMachine->GetTimeForPositionChange();
-  }
-
-  if (mElement && lastTime != mCurrentTime) {
-    UpdateReadyStateForData();
-    FireTimeUpdate();
-  }
-}
-
-void
-nsWaveDecoder::SetDuration(PRInt64 /* aDuration */)
-{
-  // Ignored by the wave decoder since we can compute the
-  // duration directly from the wave data itself.
-}
-
-void
-nsWaveDecoder::SetSeekable(PRBool aSeekable)
-{
-  mSeekable = aSeekable;
-}
-
-PRBool
-nsWaveDecoder::GetSeekable()
-{
-  return mSeekable;
-}
-
-void
-nsWaveDecoder::Suspend()
-{
-  if (mStream) {
-    mStream->Suspend(PR_TRUE);
-  }
-}
-
-void
-nsWaveDecoder::Resume(PRBool aForceBuffering)
-{
-  if (mStream) {
-    mStream->Resume();
-  }
-}
-
-void 
-nsWaveDecoder::MoveLoadsToBackground()
-{
-  if (mStream) {
-    mStream->MoveLoadsToBackground();
-  }
-}
-
-nsresult
-nsWaveDecoder::GetBuffered(nsTimeRanges* aBuffered)
-{
-  NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
-  return mPlaybackStateMachine->GetBuffered(aBuffered);
-}
--- a/content/media/wave/nsWaveDecoder.h
+++ b/content/media/wave/nsWaveDecoder.h
@@ -10,18 +10,18 @@
  *
  * 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) 2008
+ * The Initial Developer of the Original Code is the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *  Matthew Gregan <kinetik@flim.org>
  *
  * 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"),
@@ -33,276 +33,30 @@
  * 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(nsWaveDecoder_h_)
 #define nsWaveDecoder_h_
 
-#include "nsISupports.h"
-#include "nsCOMPtr.h"
-#include "nsMediaDecoder.h"
-#include "nsMediaStream.h"
-
-/*
-  nsWaveDecoder provides an implementation of the abstract nsMediaDecoder
-  class that supports parsing and playback of Waveform Audio (WAVE) chunks
-  embedded in Resource Interchange File Format (RIFF) bitstreams as
-  specified by the Multimedia Programming Interface and Data Specification
-  1.0.
-
-  Each decoder instance starts one thread (the playback thread).  A single
-  nsWaveStateMachine event is dispatched to this thread to start the
-  thread's state machine running.  The Run method of the event is a loop
-  that executes the current state.  The state can be changed by the state
-  machine, or from the main thread via threadsafe methods on the event.
-  During playback, the playback thread reads data from the network and
-  writes it to the audio backend, attempting to keep the backend's audio
-  buffers full.  It is also responsible for seeking, buffering, and
-  pausing/resuming audio.
-
-  The decoder also owns an nsMediaStream instance that provides a threadsafe
-  blocking interface to read from network channels.  The state machine is
-  the primary user of this stream and holds a weak (raw) pointer to it as
-  the thread, state machine, and stream's lifetimes are all managed by the
-  decoder.
-
-  nsWaveStateMachine has the following states:
-
-  LOADING_METADATA
-    RIFF/WAVE chunks are being read from the stream, the metadata describing
-    the audio data is parsed.
-
-  BUFFERING
-    Playback is paused while waiting for additional data.
-
-  PLAYING
-    If data is available in the stream and the audio backend can consume
-    more data, it is read from the stream and written to the audio backend.
-    Sleep until approximately half of the backend's buffers have drained.
-
-  SEEKING
-    Decoder is seeking to a specified time in the media.
-
-  PAUSED
-    Pause the audio backend, then wait for a state transition.
-
-  ENDED
-    Expected PCM data (or stream EOF) reached, wait for the audio backend to
-    play any buffered data, then wait for shutdown.
-
-  ERROR
-    Metadata loading/parsing failed, wait for shutdown.
-
-  SHUTDOWN
-    Close the audio backend and return from the run loop.
-
-  State transitions within the state machine are:
-
-  LOADING_METADATA -> PLAYING
-                   -> PAUSED
-                   -> ERROR
-
-  BUFFERING        -> PLAYING
-                   -> PAUSED
-
-  PLAYING          -> BUFFERING
-                   -> ENDED
-
-  SEEKING          -> PLAYING
-                   -> PAUSED
-
-  PAUSED           -> waits for caller to play, seek, or shutdown
-
-  ENDED            -> waits for caller to shutdown
-
-  ERROR            -> waits for caller to shutdown
-
-  SHUTDOWN         -> exits state machine
-
-  In addition, the following methods cause state transitions:
-
-  Shutdown(), Play(), Pause(), Seek(double)
-
-  The decoder implementation is currently limited to Linear PCM encoded
-  audio data with one or two channels of 8- or 16-bit samples at sample
-  rates from 100 Hz to 96 kHz.  The number of channels is limited by what
-  the audio backend (sydneyaudio via nsAudioStream) currently supports.  The
-  supported sample rate is artificially limited to arbitrarily selected sane
-  values.  Support for additional channels (and other new features) would
-  require extending nsWaveDecoder to support parsing the newer
-  WAVE_FORMAT_EXTENSIBLE chunk format.
- */
-
-class nsWaveStateMachine;
-class nsTimeRanges;
-
-class nsWaveDecoder : public nsMediaDecoder
-{
-  friend class nsWaveStateMachine;
-
-  NS_DECL_ISUPPORTS
-  NS_DECL_NSIOBSERVER
-
- public:
-  nsWaveDecoder();
-  ~nsWaveDecoder();
-
-  virtual nsMediaDecoder* Clone() { return new nsWaveDecoder(); }
-
-  virtual PRBool Init(nsHTMLMediaElement* aElement);
-
-  virtual nsMediaStream* GetCurrentStream();
-  virtual already_AddRefed<nsIPrincipal> GetCurrentPrincipal();
-
-  // Return the current playback position in the media in seconds.
-  virtual double GetCurrentTime();
-
-  // Return the total playback length of the media in seconds.
-  virtual double GetDuration();
-
-  // Set the audio playback volume; must be in range [0.0, 1.0].
-  virtual void SetVolume(double aVolume);
-
-  virtual nsresult Play();
-  virtual void Pause();
-
-  // Set the current time of the media to aTime.  This may cause mStream to
-  // create a new channel to fetch data from the appropriate position in the
-  // stream.
-  virtual nsresult Seek(double aTime);
+#include "nsBuiltinDecoder.h"
 
-  // Report whether the decoder is currently seeking.
-  virtual PRBool IsSeeking() const;
-
-  // Report whether the decoder has reached end of playback.
-  virtual PRBool IsEnded() const;
-
-  // Start downloading the media at the specified URI.  The media's metadata
-  // will be parsed and made available as the load progresses.
-  virtual nsresult Load(nsMediaStream* aStream,
-                        nsIStreamListener** aStreamListener,
-                        nsMediaDecoder* aCloneDonor);
-
-  // Called by mStream (and possibly the nsChannelToPipeListener used
-  // internally by mStream) when the stream has completed loading.
-  virtual void ResourceLoaded();
-
-  // Called by mStream (and possibly the nsChannelToPipeListener used
-  // internally by mStream) if the stream encounters a network error.
-  virtual void NetworkError();
-
-  // Element is notifying us that the requested playback rate has changed.
-  virtual nsresult PlaybackRateChanged();
-
-  virtual void NotifySuspendedStatusChanged();
-  virtual void NotifyBytesDownloaded();
-  virtual void NotifyDownloadEnded(nsresult aStatus);
-
-  virtual Statistics GetStatistics();
-
-  void PlaybackPositionChanged();
-
-  // Setter for the duration. This is ignored by the wave decoder since it can
-  // compute the duration directly from the wave data.
-  virtual void SetDuration(PRInt64 aDuration);
-
-  // Getter/setter for mSeekable.
-  virtual void SetSeekable(PRBool aSeekable);
-  virtual PRBool GetSeekable();
-
-  // Must be called by the owning object before disposing the decoder.
-  virtual void Shutdown();
-
-  // Suspend any media downloads that are in progress. Called by the
-  // media element when it is sent to the bfcache. Call on the main
-  // thread only.
-  virtual void Suspend();
-
-  // Resume any media downloads that have been suspended. Called by the
-  // media element when it is restored from the bfcache. Call on the
-  // main thread only.
-  virtual void Resume(PRBool aForceBuffering);
-
-  // Calls mElement->UpdateReadyStateForData, telling it which state we have
-  // entered.  Main thread only.
-  void NextFrameUnavailableBuffering();
-  void NextFrameAvailable();
-  void NextFrameUnavailable();
-
-  // Change the element's ready state as necessary. Main thread only.
-  void UpdateReadyStateForData();
-
-  // Tells mStream to put all loads in the background.
-  virtual void MoveLoadsToBackground();
+/**
+ * The decoder implementation is currently limited to Linear PCM encoded
+ * audio data with one or two channels of 8- or 16-bit samples at sample
+ * rates from 100 Hz to 96 kHz.  The number of channels is limited by what
+ * the audio backend (sydneyaudio via nsAudioStream) currently supports.  The
+ * supported sample rate is artificially limited to arbitrarily selected sane
+ * values.  Support for additional channels (and other new features) would
+ * require extending nsWaveDecoder to support parsing the newer
+ * WAVE_FORMAT_EXTENSIBLE chunk format.
+**/
 
-  // Called asynchronously to shut down the decoder
-  void Stop();
 
-  // Constructs the time ranges representing what segments of the media
-  // are buffered and playable.
-  virtual nsresult GetBuffered(nsTimeRanges* aBuffered);
-
-  virtual void NotifyDataArrived(const char* aBuffer, PRUint32 aLength, PRUint32 aOffset) {}
-
-private:
-  // Notifies the element that seeking has started.
-  void SeekingStarted();
-
-  // Notifies the element that seeking has completed.
-  void SeekingStopped();
-
-  // Notifies the element that metadata loading has completed.  Only fired
-  // if metadata is valid.
-  void MetadataLoaded();
-
-  // Notifies the element that playback has completed.
-  void PlaybackEnded();
-
-  // Notifies the element that decoding has failed.
-  void DecodeError();
-
-  // Ensures that state machine thread is running, starting a new one
-  // if necessary.
-  nsresult StartStateMachineThread();
-
-  // Volume that the audio backend will be initialized with.
-  double mInitialVolume;
-
-  // Thread that handles audio playback, including data download.
-  nsCOMPtr<nsIThread> mPlaybackThread;
-
-  // State machine that runs on mPlaybackThread.  Methods on this object are
-  // safe to call from any thread.
-  nsCOMPtr<nsWaveStateMachine> mPlaybackStateMachine;
-
-  // Threadsafe wrapper around channels that provides seeking based on the
-  // underlying channel type.
-  nsAutoPtr<nsMediaStream> mStream;
-
-  // The current playback position of the media resource in units of
-  // seconds. This is updated every time a block of audio is passed to the
-  // backend (unless an prior update is still pending).  It is read and
-  // written from the main thread only.
-  double mCurrentTime;
-
-  // Copy of the duration and ended state when the state machine was
-  // disposed.  Used to respond to duration and ended queries with sensible
-  // values after the state machine has been destroyed.
-  double mEndedDuration;
-  PRPackedBool mEnded;
-
-  // True if the media resource is seekable.
-  PRPackedBool mSeekable;
-
-  // True when the media resource has completely loaded. Accessed on
-  // the main thread only.
-  PRPackedBool mResourceLoaded;
-
-  // True if MetadataLoaded has been reported to the element.
-  PRPackedBool mMetadataLoadedReported;
-
-  // True if ResourceLoaded has been reported to the element.
-  PRPackedBool mResourceLoadedReported;
+class nsWaveDecoder : public nsBuiltinDecoder
+{
+public:
+   virtual nsMediaDecoder* Clone() { return new nsWaveDecoder(); }
+   virtual nsDecoderStateMachine* CreateStateMachine();
 };
 
 #endif
new file mode 100644
--- /dev/null
+++ b/content/media/wave/nsWaveReader.cpp
@@ -0,0 +1,551 @@
+/* -*- 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 Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Matthew Gregan <kinetik@flim.org>
+ *
+ * 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 "nsError.h"
+#include "nsBuiltinDecoderStateMachine.h"
+#include "nsBuiltinDecoder.h"
+#include "nsMediaStream.h"
+#include "nsWaveReader.h"
+#include "nsTimeRanges.h"
+#include "VideoUtils.h"
+
+using namespace mozilla;
+
+// Un-comment to enable logging of seek bisections.
+//#define SEEK_LOGGING
+
+#ifdef PR_LOGGING
+extern PRLogModuleInfo* gBuiltinDecoderLog;
+#define LOG(type, msg) PR_LOG(gBuiltinDecoderLog, type, msg)
+#ifdef SEEK_LOGGING
+#define SEEK_LOG(type, msg) PR_LOG(gBuiltinDecoderLog, type, msg)
+#else
+#define SEEK_LOG(type, msg)
+#endif
+#else
+#define LOG(type, msg)
+#define SEEK_LOG(type, msg)
+#endif
+
+// Magic values that identify RIFF chunks we're interested in.
+#define RIFF_CHUNK_MAGIC 0x52494646
+#define WAVE_CHUNK_MAGIC 0x57415645
+#define FRMT_CHUNK_MAGIC 0x666d7420
+#define DATA_CHUNK_MAGIC 0x64617461
+
+// Size of RIFF chunk header.  4 byte chunk header type and 4 byte size field.
+#define RIFF_CHUNK_HEADER_SIZE 8
+
+// Size of RIFF header.  RIFF chunk and 4 byte RIFF type.
+#define RIFF_INITIAL_SIZE (RIFF_CHUNK_HEADER_SIZE + 4)
+
+// Size of required part of format chunk.  Actual format chunks may be
+// extended (for non-PCM encodings), but we skip any extended data.
+#define WAVE_FORMAT_CHUNK_SIZE 16
+
+// PCM encoding type from format chunk.  Linear PCM is the only encoding
+// supported by nsAudioStream.
+#define WAVE_FORMAT_ENCODING_PCM 1
+
+// Maximum number of channels supported
+#define MAX_CHANNELS 2
+
+namespace {
+  PRUint32
+  ReadUint32BE(const char** aBuffer)
+  {
+    PRUint32 result =
+      PRUint8((*aBuffer)[0]) << 24 |
+      PRUint8((*aBuffer)[1]) << 16 |
+      PRUint8((*aBuffer)[2]) << 8 |
+      PRUint8((*aBuffer)[3]);
+    *aBuffer += sizeof(PRUint32);
+    return result;
+  }
+
+  PRUint32
+  ReadUint32LE(const char** aBuffer)
+  {
+    PRUint32 result =
+      PRUint8((*aBuffer)[3]) << 24 |
+      PRUint8((*aBuffer)[2]) << 16 |
+      PRUint8((*aBuffer)[1]) << 8 |
+      PRUint8((*aBuffer)[0]);
+    *aBuffer += sizeof(PRUint32);
+    return result;
+  }
+
+  PRUint16
+  ReadUint16LE(const char** aBuffer)
+  {
+    PRUint16 result =
+      PRUint8((*aBuffer)[1]) << 8 |
+      PRUint8((*aBuffer)[0]) << 0;
+    *aBuffer += sizeof(PRUint16);
+    return result;
+  }
+
+  PRInt16
+  ReadInt16LE(const char** aBuffer)
+  {
+    return static_cast<PRInt16>(ReadUint16LE(aBuffer));
+  }
+
+  PRUint8
+  ReadUint8(const char** aBuffer)
+  {
+    PRUint8 result = PRUint8((*aBuffer)[0]);
+    *aBuffer += sizeof(PRUint8);
+    return result;
+  }
+}
+
+nsWaveReader::nsWaveReader(nsBuiltinDecoder* aDecoder)
+  : nsBuiltinDecoderReader(aDecoder)
+{
+  MOZ_COUNT_CTOR(nsWaveReader);
+}
+
+nsWaveReader::~nsWaveReader()
+{
+  MOZ_COUNT_DTOR(nsWaveReader);
+}
+
+nsresult nsWaveReader::Init(nsBuiltinDecoderReader* aCloneDonor)
+{
+  return NS_OK;
+}
+
+nsresult nsWaveReader::ReadMetadata(nsVideoInfo* aInfo)
+{
+  NS_ASSERTION(mDecoder->OnStateMachineThread(), "Should be on state machine thread.");
+  MonitorAutoEnter mon(mMonitor);
+
+  PRBool loaded = LoadRIFFChunk() && LoadFormatChunk() && FindDataOffset();
+  if (!loaded) {
+    return NS_ERROR_FAILURE;
+  }
+
+  mInfo.mHasAudio = PR_TRUE;
+  mInfo.mHasVideo = PR_FALSE;
+  mInfo.mAudioRate = mSampleRate;
+  mInfo.mAudioChannels = mChannels;
+  mInfo.mDataOffset = -1;
+
+  *aInfo = mInfo;
+
+  MonitorAutoExit exitReaderMon(mMonitor);
+  MonitorAutoEnter decoderMon(mDecoder->GetMonitor());
+
+  float d = floorf(BytesToTime(GetDataLength() * 1000));
+  NS_ASSERTION(d <= PR_INT64_MAX, "Duration overflow");
+  mDecoder->GetStateMachine()->SetDuration(static_cast<PRInt64>(d));
+
+  return NS_OK;
+}
+
+PRBool nsWaveReader::DecodeAudioData()
+{
+  MonitorAutoEnter mon(mMonitor);
+  NS_ASSERTION(mDecoder->OnStateMachineThread() || mDecoder->OnDecodeThread(),
+               "Should be on state machine thread or decode thread.");
+
+  PRInt64 pos = GetPosition();
+  PRInt64 len = GetDataLength();
+  PRInt64 remaining = len - pos;
+  NS_ASSERTION(remaining >= 0, "Current wave position is greater than wave file length");
+
+  static const PRInt64 BLOCK_SIZE = 4096;
+  PRInt64 readSize = NS_MIN(BLOCK_SIZE, remaining);
+  PRInt64 samples = readSize / mSampleSize;
+
+  PR_STATIC_ASSERT(PRUint64(BLOCK_SIZE) < UINT_MAX / sizeof(SoundDataValue) / MAX_CHANNELS);
+  const size_t bufferSize = static_cast<size_t>(samples * mChannels);
+  nsAutoArrayPtr<SoundDataValue> sampleBuffer(new SoundDataValue[bufferSize]);
+
+  PR_STATIC_ASSERT(PRUint64(BLOCK_SIZE) < UINT_MAX / sizeof(char));
+  nsAutoArrayPtr<char> dataBuffer(new char[static_cast<size_t>(readSize)]);
+
+  if (!ReadAll(dataBuffer, readSize)) {
+    mAudioQueue.Finish();
+    return PR_FALSE;
+  }
+
+  // convert data to samples
+  const char* d = dataBuffer.get();
+  SoundDataValue* s = sampleBuffer.get();
+  for (int i = 0; i < samples; ++i) {
+    for (unsigned int j = 0; j < mChannels; ++j) {
+      if (mSampleFormat == nsAudioStream::FORMAT_U8) {
+        PRUint8 v =  ReadUint8(&d);
+#if defined(MOZ_SAMPLE_TYPE_S16LE)
+        *s++ = (v * (1.F/PR_UINT8_MAX)) * PR_UINT16_MAX + PR_INT16_MIN;
+#elif defined(MOZ_SAMPLE_TYPE_FLOAT32)
+        *s++ = (v * (1.F/PR_UINT8_MAX)) * 2.F - 1.F;
+#endif
+      }
+      else if (mSampleFormat == nsAudioStream::FORMAT_S16_LE) {
+        PRInt16 v =  ReadInt16LE(&d);
+#if defined(MOZ_SAMPLE_TYPE_S16LE)
+        *s++ = v;
+#elif defined(MOZ_SAMPLE_TYPE_FLOAT32)
+        *s++ = (PRInt32(v) - PR_INT16_MIN) / float(PR_UINT16_MAX) * 2.F - 1.F;
+#endif
+      }
+    }
+  }
+
+  float posTime = BytesToTime(pos);
+  float readSizeTime = BytesToTime(readSize);
+  NS_ASSERTION(posTime <= PR_INT64_MAX / 1000, "posTime overflow");
+  NS_ASSERTION(readSizeTime <= PR_INT64_MAX / 1000, "readSizeTime overflow");
+  NS_ASSERTION(samples < PR_INT32_MAX, "samples overflow");
+
+  mAudioQueue.Push(new SoundData(pos, static_cast<PRInt64>(posTime * 1000),
+                                 static_cast<PRInt64>(readSizeTime * 1000),
+                                 static_cast<PRInt32>(samples),
+                                 sampleBuffer.forget(), mChannels));
+
+  return PR_TRUE;
+}
+
+PRBool nsWaveReader::DecodeVideoFrame(PRBool &aKeyframeSkip,
+                                      PRInt64 aTimeThreshold)
+{
+  MonitorAutoEnter mon(mMonitor);
+  NS_ASSERTION(mDecoder->OnStateMachineThread() || mDecoder->OnDecodeThread(),
+               "Should be on state machine or decode thread.");
+
+  return PR_FALSE;
+}
+
+nsresult nsWaveReader::Seek(PRInt64 aTarget, PRInt64 aStartTime, PRInt64 aEndTime, PRInt64 aCurrentTime)
+{
+  MonitorAutoEnter mon(mMonitor);
+  NS_ASSERTION(mDecoder->OnStateMachineThread(),
+               "Should be on state machine thread.");
+  LOG(PR_LOG_DEBUG, ("%p About to seek to %lldms", mDecoder, aTarget));
+  if (NS_FAILED(ResetDecode())) {
+    return NS_ERROR_FAILURE;
+  }
+  float d = BytesToTime(GetDataLength());
+  NS_ASSERTION(d < PR_INT64_MAX / 1000, "Duration overflow"); 
+  PRInt64 duration = static_cast<PRInt64>(d) * 1000;
+  PRInt64 seekTime = NS_MIN(aTarget, duration);
+  PRInt64 position = RoundDownToSample(static_cast<PRInt64>(TimeToBytes(seekTime) / 1000.f));
+  NS_ASSERTION(PR_INT64_MAX - mWavePCMOffset > position, "Integer overflow during wave seek");
+  position += mWavePCMOffset;
+  return mDecoder->GetCurrentStream()->Seek(nsISeekableStream::NS_SEEK_SET, position);
+}
+
+nsresult nsWaveReader::GetBuffered(nsTimeRanges* aBuffered, PRInt64 aStartTime)
+{
+  PRInt64 startOffset = mDecoder->GetCurrentStream()->GetNextCachedData(mWavePCMOffset);
+  while (startOffset >= 0) {
+    PRInt64 endOffset = mDecoder->GetCurrentStream()->GetCachedDataEnd(startOffset);
+    // Bytes [startOffset..endOffset] are cached.
+    NS_ASSERTION(startOffset >= mWavePCMOffset, "Integer underflow in GetBuffered");
+    NS_ASSERTION(endOffset >= mWavePCMOffset, "Integer underflow in GetBuffered");
+
+    aBuffered->Add(floorf(BytesToTime(startOffset - mWavePCMOffset) * 1000.f) / 1000.0,
+                   floorf(BytesToTime(endOffset - mWavePCMOffset) * 1000.f) / 1000.0);
+    startOffset = mDecoder->GetCurrentStream()->GetNextCachedData(endOffset);
+  }
+  return NS_OK;
+}
+
+PRBool
+nsWaveReader::ReadAll(char* aBuf, PRInt64 aSize, PRInt64* aBytesRead)
+{
+  PRUint32 got = 0;
+  if (aBytesRead) {
+    *aBytesRead = 0;
+  }
+  do {
+    PRUint32 read = 0;
+    if (NS_FAILED(mDecoder->GetCurrentStream()->Read(aBuf + got, PRUint32(aSize - got), &read))) {
+      NS_WARNING("Stream read failed");
+      return PR_FALSE;
+    }
+    if (read == 0) {
+      return PR_FALSE;
+    }
+    mDecoder->NotifyBytesConsumed(read);
+    got += read;
+    if (aBytesRead) {
+      *aBytesRead = got;
+    }
+  } while (got != aSize);
+  return PR_TRUE;
+}
+
+PRBool
+nsWaveReader::LoadRIFFChunk()
+{
+  char riffHeader[RIFF_INITIAL_SIZE];
+  const char* p = riffHeader;
+
+  NS_ABORT_IF_FALSE(mDecoder->GetCurrentStream()->Tell() == 0,
+                    "LoadRIFFChunk called when stream in invalid state");
+
+  if (!ReadAll(riffHeader, sizeof(riffHeader))) {
+    return PR_FALSE;
+  }
+
+  PR_STATIC_ASSERT(sizeof(PRUint32) * 2 <= RIFF_INITIAL_SIZE);
+  if (ReadUint32BE(&p) != RIFF_CHUNK_MAGIC) {
+    NS_WARNING("Stream data not in RIFF format");
+    return PR_FALSE;
+  }
+
+  // Skip over RIFF size field.
+  p += 4;
+
+  if (ReadUint32BE(&p) != WAVE_CHUNK_MAGIC) {
+    NS_WARNING("Expected WAVE chunk");
+    return PR_FALSE;
+  }
+
+  return PR_TRUE;
+}
+
+PRBool
+nsWaveReader::ScanForwardUntil(PRUint32 aWantedChunk, PRUint32* aChunkSize)
+{
+  NS_ABORT_IF_FALSE(aChunkSize, "Require aChunkSize argument");
+  *aChunkSize = 0;
+
+  for (;;) {
+    static const unsigned int CHUNK_HEADER_SIZE = 8;
+    char chunkHeader[CHUNK_HEADER_SIZE];
+    const char* p = chunkHeader;
+
+    if (!ReadAll(chunkHeader, sizeof(chunkHeader))) {
+      return PR_FALSE;
+    }
+
+    PR_STATIC_ASSERT(sizeof(PRUint32) * 2 <= CHUNK_HEADER_SIZE);
+    PRUint32 magic = ReadUint32BE(&p);
+    PRUint32 chunkSize = ReadUint32LE(&p);
+
+    if (magic == aWantedChunk) {
+      *aChunkSize = chunkSize;
+      return PR_TRUE;
+    }
+
+    // RIFF chunks are two-byte aligned, so round up if necessary.
+    chunkSize += chunkSize % 2;
+
+    static const unsigned int MAX_CHUNK_SIZE = 1 << 16;
+    PR_STATIC_ASSERT(MAX_CHUNK_SIZE < UINT_MAX / sizeof(char));
+    nsAutoArrayPtr<char> chunk(new char[MAX_CHUNK_SIZE]);
+    while (chunkSize > 0) {
+      PRUint32 size = PR_MIN(chunkSize, MAX_CHUNK_SIZE);
+      if (!ReadAll(chunk.get(), size)) {
+        return PR_FALSE;
+      }
+      chunkSize -= size;
+    }
+  }
+}
+
+PRBool
+nsWaveReader::LoadFormatChunk()
+{
+  PRUint32 fmtSize, rate, channels, sampleSize, sampleFormat;
+  char waveFormat[WAVE_FORMAT_CHUNK_SIZE];
+  const char* p = waveFormat;
+
+  // RIFF chunks are always word (two byte) aligned.
+  NS_ABORT_IF_FALSE(mDecoder->GetCurrentStream()->Tell() % 2 == 0,
+                    "LoadFormatChunk called with unaligned stream");
+
+  // The "format" chunk may not directly follow the "riff" chunk, so skip
+  // over any intermediate chunks.
+  if (!ScanForwardUntil(FRMT_CHUNK_MAGIC, &fmtSize)) {
+    return PR_FALSE;
+  }
+
+  if (!ReadAll(waveFormat, sizeof(waveFormat))) {
+    return PR_FALSE;
+  }
+
+  PR_STATIC_ASSERT(sizeof(PRUint16) +
+                   sizeof(PRUint16) +
+                   sizeof(PRUint32) +
+                   4 +
+                   sizeof(PRUint16) +
+                   sizeof(PRUint16) <= sizeof(waveFormat));
+  if (ReadUint16LE(&p) != WAVE_FORMAT_ENCODING_PCM) {
+    NS_WARNING("WAVE is not uncompressed PCM, compressed encodings are not supported");
+    return PR_FALSE;
+  }
+
+  channels = ReadUint16LE(&p);
+  rate = ReadUint32LE(&p);
+
+  // Skip over average bytes per second field.
+  p += 4;
+
+  sampleSize = ReadUint16LE(&p);
+
+  sampleFormat = ReadUint16LE(&p);
+
+  // PCM encoded WAVEs are not expected to have an extended "format" chunk,
+  // but I have found WAVEs that have a extended "format" chunk with an
+  // extension size of 0 bytes.  Be polite and handle this rather than
+  // considering the file invalid.  This code skips any extension of the
+  // "format" chunk.
+  if (fmtSize > WAVE_FORMAT_CHUNK_SIZE) {
+    char extLength[2];
+    const char* p = extLength;
+
+    if (!ReadAll(extLength, sizeof(extLength))) {
+      return PR_FALSE;
+    }
+
+    PR_STATIC_ASSERT(sizeof(PRUint16) <= sizeof(extLength));
+    PRUint16 extra = ReadUint16LE(&p);
+    if (fmtSize - (WAVE_FORMAT_CHUNK_SIZE + 2) != extra) {
+      NS_WARNING("Invalid extended format chunk size");
+      return PR_FALSE;
+    }
+    extra += extra % 2;
+
+    if (extra > 0) {
+      PR_STATIC_ASSERT(PR_UINT16_MAX + (PR_UINT16_MAX % 2) < UINT_MAX / sizeof(char));
+      nsAutoArrayPtr<char> chunkExtension(new char[extra]);
+      if (!ReadAll(chunkExtension.get(), extra)) {
+        return PR_FALSE;
+      }
+    }
+  }
+
+  // RIFF chunks are always word (two byte) aligned.
+  NS_ABORT_IF_FALSE(mDecoder->GetCurrentStream()->Tell() % 2 == 0,
+                    "LoadFormatChunk left stream unaligned");
+
+  // Make sure metadata is fairly sane.  The rate check is fairly arbitrary,
+  // but the channels check is intentionally limited to mono or stereo
+  // because that's what the audio backend currently supports.
+  if (rate < 100 || rate > 96000 ||
+      channels < 1 || channels > MAX_CHANNELS ||
+      (sampleSize != 1 && sampleSize != 2 && sampleSize != 4) ||
+      (sampleFormat != 8 && sampleFormat != 16)) {
+    NS_WARNING("Invalid WAVE metadata");
+    return PR_FALSE;
+  }
+
+  MonitorAutoEnter monitor(mDecoder->GetMonitor());
+  mSampleRate = rate;
+  mChannels = channels;
+  mSampleSize = sampleSize;
+  if (sampleFormat == 8) {
+    mSampleFormat = nsAudioStream::FORMAT_U8;
+  } else {
+    mSampleFormat = nsAudioStream::FORMAT_S16_LE;
+  }
+  return PR_TRUE;
+}
+
+PRBool
+nsWaveReader::FindDataOffset()
+{
+  // RIFF chunks are always word (two byte) aligned.
+  NS_ABORT_IF_FALSE(mDecoder->GetCurrentStream()->Tell() % 2 == 0,
+                    "FindDataOffset called with unaligned stream");
+
+  // The "data" chunk may not directly follow the "format" chunk, so skip
+  // over any intermediate chunks.
+  PRUint32 length;
+  if (!ScanForwardUntil(DATA_CHUNK_MAGIC, &length)) {
+    return PR_FALSE;
+  }
+
+  PRInt64 offset = mDecoder->GetCurrentStream()->Tell();
+  if (offset <= 0 || offset > PR_UINT32_MAX) {
+    NS_WARNING("PCM data offset out of range");
+    return PR_FALSE;
+  }
+
+  MonitorAutoEnter monitor(mDecoder->GetMonitor());
+  mWaveLength = length;
+  mWavePCMOffset = PRUint32(offset);
+  return PR_TRUE;
+}
+
+float
+nsWaveReader::BytesToTime(PRInt64 aBytes) const
+{
+  NS_ABORT_IF_FALSE(aBytes >= 0, "Must be >= 0");
+  return float(aBytes) / mSampleRate / mSampleSize;
+}
+
+PRInt64
+nsWaveReader::TimeToBytes(float aTime) const
+{
+  NS_ABORT_IF_FALSE(aTime >= 0.0f, "Must be >= 0");
+  return RoundDownToSample(PRInt64(aTime * mSampleRate * mSampleSize));
+}
+
+PRInt64
+nsWaveReader::RoundDownToSample(PRInt64 aBytes) const
+{
+  NS_ABORT_IF_FALSE(aBytes >= 0, "Must be >= 0");
+  return aBytes - (aBytes % mSampleSize);
+}
+
+PRInt64
+nsWaveReader::GetDataLength()
+{
+  PRInt64 length = mWaveLength;
+  // If the decoder has a valid content length, and it's shorter than the
+  // expected length of the PCM data, calculate the playback duration from
+  // the content length rather than the expected PCM data length.
+  PRInt64 streamLength = mDecoder->GetCurrentStream()->GetLength();
+  if (streamLength >= 0) {
+    PRInt64 dataLength = PR_MAX(0, streamLength - mWavePCMOffset);
+    length = PR_MIN(dataLength, length);
+  }
+  return length;
+}
+
+PRInt64
+nsWaveReader::GetPosition()
+{
+  return mDecoder->GetCurrentStream()->Tell();
+}
new file mode 100644
--- /dev/null
+++ b/content/media/wave/nsWaveReader.h
@@ -0,0 +1,120 @@
+/* -*- 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 Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Matthew Gregan <kinetik@flim.org>
+ *
+ * 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(nsWaveReader_h_)
+#define nsWaveReader_h_
+
+#include "nsBuiltinDecoderReader.h"
+
+class nsMediaDecoder;
+
+class nsWaveReader : public nsBuiltinDecoderReader
+{
+public:
+  nsWaveReader(nsBuiltinDecoder* aDecoder);
+  ~nsWaveReader();
+
+  virtual nsresult Init(nsBuiltinDecoderReader* aCloneDonor);
+  virtual PRBool DecodeAudioData();
+  virtual PRBool DecodeVideoFrame(PRBool &aKeyframeSkip,
+                                  PRInt64 aTimeThreshold);
+
+  virtual PRBool HasAudio()
+  {
+    return PR_TRUE;
+  }
+
+  virtual PRBool HasVideo()
+  {
+    return PR_FALSE;
+  }
+
+  virtual nsresult ReadMetadata(nsVideoInfo* aInfo);
+  virtual nsresult Seek(PRInt64 aTime, PRInt64 aStartTime, PRInt64 aEndTime, PRInt64 aCurrentTime);
+  virtual nsresult GetBuffered(nsTimeRanges* aBuffered, PRInt64 aStartTime);
+
+private:
+  PRBool ReadAll(char* aBuf, PRInt64 aSize, PRInt64* aBytesRead = nsnull);
+  PRBool LoadRIFFChunk();
+  PRBool ScanForwardUntil(PRUint32 aWantedChunk, PRUint32* aChunkSize);
+  PRBool LoadFormatChunk();
+  PRBool FindDataOffset();
+
+  // Returns the number of seconds that aBytes represents based on the
+  // current audio parameters.  e.g.  176400 bytes is 1 second at 16-bit
+  // stereo 44.1kHz. The time is rounded to the nearest millisecond.
+  float BytesToTime(PRInt64 aBytes) const;
+
+  // Returns the number of bytes that aTime represents based on the current
+  // audio parameters.  e.g.  1 second is 176400 bytes at 16-bit stereo
+  // 44.1kHz.
+  PRInt64 TimeToBytes(float aTime) const;
+
+  // Rounds aBytes down to the nearest complete sample.  Assumes beginning
+  // of byte range is already sample aligned by caller.
+  PRInt64 RoundDownToSample(PRInt64 aBytes) const;
+  PRInt64 GetDataLength();
+  PRInt64 GetPosition();
+
+  /*
+    Metadata extracted from the WAVE header.  Used to initialize the audio
+    stream, and for byte<->time domain conversions.
+  */
+
+  // Number of samples per second.  Limited to range [100, 96000] in LoadFormatChunk.
+  PRUint32 mSampleRate;
+
+  // Number of channels.  Limited to range [1, 2] in LoadFormatChunk.
+  PRUint32 mChannels;
+
+  // Size of a single sample segment, which includes a sample for each
+  // channel (interleaved).
+  PRUint32 mSampleSize;
+
+  // The sample format of the PCM data.
+  nsAudioStream::SampleFormat mSampleFormat;
+
+  // Size of PCM data stored in the WAVE as reported by the data chunk in
+  // the media.
+  PRInt64 mWaveLength;
+
+  // Start offset of the PCM data in the media stream.  Extends mWaveLength
+  // bytes.
+  PRInt64 mWavePCMOffset;
+};
+
+#endif