Bug 648595 - Part 1/2: Implement temporal dimension portion of Media Fragments URI specification for video/audio - r=cpearce
authorChris Double <chris.double@double.co.nz>
Thu, 25 Aug 2011 11:42:23 +1200
changeset 76348 dc1d14484d6ee95306c8946b04e46b0183ce2d9b
parent 76347 dddbe27e21a817be7907727196654dd2b877747d
child 76349 1a1dbcc15b7e7374a5c1368e5a29ab0060feef5d
push idunknown
push userunknown
push dateunknown
reviewerscpearce
bugs648595
milestone9.0a1
Bug 648595 - Part 1/2: Implement temporal dimension portion of Media Fragments URI specification for video/audio - r=cpearce
content/html/content/public/nsHTMLMediaElement.h
content/html/content/src/Makefile.in
content/html/content/src/nsHTMLMediaElement.cpp
content/html/content/src/nsMediaFragmentURIParser.cpp
content/html/content/src/nsMediaFragmentURIParser.h
content/media/nsBuiltinDecoder.cpp
content/media/nsBuiltinDecoder.h
content/media/nsBuiltinDecoderStateMachine.cpp
content/media/nsBuiltinDecoderStateMachine.h
content/media/nsMediaDecoder.h
dom/interfaces/html/nsIDOMHTMLMediaElement.idl
--- a/content/html/content/public/nsHTMLMediaElement.h
+++ b/content/html/content/public/nsHTMLMediaElement.h
@@ -520,16 +520,26 @@ protected:
   void DispatchAsyncSourceError(nsIContent* aSourceElement);
 
   /**
    * Resets the media element for an error condition as per aErrorCode.
    * aErrorCode must be one of nsIDOMHTMLMediaError codes.
    */
   void Error(PRUint16 aErrorCode);
 
+  /**
+   * Returns the URL spec of the currentSrc.
+   **/
+  void GetCurrentSpec(nsCString& aString);
+
+  /**
+   * Process any media fragment entries in the URI
+   */
+  void ProcessMediaFragmentURI();
+
   nsRefPtr<nsMediaDecoder> mDecoder;
 
   // A reference to the ImageContainer which contains the current frame
   // of video to display.
   nsRefPtr<ImageContainer> mImageContainer;
 
   // Holds a reference to the first channel we open to the media resource.
   // Once the decoder is created, control over the channel passes to the
@@ -606,16 +616,26 @@ protected:
   // Time that the last timeupdate event was fired. Read/Write from the
   // main thread only.
   TimeStamp mTimeUpdateTime;
 
   // Media 'currentTime' value when the last timeupdate event occurred.
   // Read/Write from the main thread only.
   double mLastCurrentTime;
 
+  // Logical start time of the media resource in seconds as obtained
+  // from any media fragments. A negative value indicates that no
+  // fragment time has been set. Read/Write from the main thread only.
+  double mFragmentStart;
+
+  // Logical end time of the media resource in seconds as obtained
+  // from any media fragments. A negative value indicates that no
+  // fragment time has been set. Read/Write from the main thread only.
+  double mFragmentEnd;
+
   nsRefPtr<gfxASurface> mPrintSurface;
 
   // Reference to the source element last returned by GetNextSource().
   // This is the child source element which we're trying to load from.
   nsCOMPtr<nsIContent> mSourceLoadCandidate;
 
   // An audio stream for writing audio directly from JS.
   nsRefPtr<nsAudioStream> mAudioStream;
--- a/content/html/content/src/Makefile.in
+++ b/content/html/content/src/Makefile.in
@@ -115,16 +115,17 @@ CPPSRCS		= \
 		nsDOMStringMap.cpp \
 		$(NULL)
 
 ifdef MOZ_MEDIA
 CPPSRCS		+= \
 		nsHTMLAudioElement.cpp \
 		nsHTMLMediaElement.cpp \
 		nsMediaError.cpp \
+		nsMediaFragmentURIParser.cpp \
 		nsHTMLSourceElement.cpp \
 		nsTimeRanges.cpp \
 		nsHTMLVideoElement.cpp \
 		$(NULL)
 endif
 
 # we don't want the shared lib, but we want to force the creation of a static lib.
 FORCE_STATIC_LIB = 1
--- a/content/html/content/src/nsHTMLMediaElement.cpp
+++ b/content/html/content/src/nsHTMLMediaElement.cpp
@@ -54,17 +54,16 @@
 #include "nsNetUtil.h"
 #include "prmem.h"
 #include "nsNetUtil.h"
 #include "nsXPCOMStrings.h"
 #include "prlock.h"
 #include "nsThreadUtils.h"
 #include "nsIThreadInternal.h"
 #include "nsContentUtils.h"
-
 #include "nsFrameManager.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsIXPConnect.h"
 #include "jsapi.h"
 
 #include "nsITimer.h"
 
 #include "nsEventDispatcher.h"
@@ -86,16 +85,17 @@
 #include <limits>
 #include "nsIDocShellTreeItem.h"
 #include "nsIAsyncVerifyRedirectCallback.h"
 #include "nsIAppShell.h"
 #include "nsWidgetsCID.h"
 
 #include "nsIPrivateDOMEvent.h"
 #include "nsIDOMNotifyAudioAvailableEvent.h"
+#include "nsMediaFragmentURIParser.h"
 
 #ifdef MOZ_OGG
 #include "nsOggDecoder.h"
 #endif
 #ifdef MOZ_WAVE
 #include "nsWaveDecoder.h"
 #endif
 #ifdef MOZ_WEBM
@@ -424,28 +424,18 @@ NS_IMETHODIMP nsHTMLMediaElement::GetEnd
 
   return NS_OK;
 }
 
 /* readonly attribute DOMString currentSrc; */
 NS_IMETHODIMP nsHTMLMediaElement::GetCurrentSrc(nsAString & aCurrentSrc)
 {
   nsCAutoString src;
-
-  if (mDecoder) {
-    nsMediaStream* stream = mDecoder->GetCurrentStream();
-    if (stream) {
-      stream->URI()->GetSpec(src);
-    }
-  } else if (mLoadingSrc) {
-    mLoadingSrc->GetSpec(src);
-  }
-
+  GetCurrentSpec(src);
   aCurrentSrc = NS_ConvertUTF8toUTF16(src);
-
   return NS_OK;
 }
 
 /* readonly attribute unsigned short networkState; */
 NS_IMETHODIMP nsHTMLMediaElement::GetNetworkState(PRUint16 *aNetworkState)
 {
   *aNetworkState = mNetworkState;
 
@@ -1287,16 +1277,18 @@ nsHTMLMediaElement::nsHTMLMediaElement(a
     mReadyState(nsIDOMHTMLMediaElement::HAVE_NOTHING),
     mLoadWaitStatus(NOT_WAITING),
     mVolume(1.0),
     mChannels(0),
     mRate(0),
     mPreloadAction(PRELOAD_UNDEFINED),
     mMediaSize(-1,-1),
     mLastCurrentTime(0.0),
+    mFragmentStart(-1.0),
+    mFragmentEnd(-1.0),
     mAllowAudioData(PR_FALSE),
     mBegun(PR_FALSE),
     mLoadedFirstFrame(PR_FALSE),
     mAutoplaying(PR_TRUE),
     mAutoplayEnabled(PR_TRUE),
     mPaused(PR_TRUE),
     mMuted(PR_FALSE),
     mPlayingBeforeSeek(PR_FALSE),
@@ -1947,23 +1939,50 @@ nsresult nsHTMLMediaElement::NewURIFromS
     // decode it.
     NS_RELEASE(*aURI);
     return NS_ERROR_DOM_INVALID_STATE_ERR;
   }
 
   return NS_OK;
 }
 
+void nsHTMLMediaElement::ProcessMediaFragmentURI()
+{
+  nsCAutoString ref;
+  GetCurrentSpec(ref);
+  nsMediaFragmentURIParser parser(ref);
+  parser.Parse();
+  double start = parser.GetStartTime();
+  if (mDecoder) {
+    double end = parser.GetEndTime();
+    if (end < 0.0 || end > start) {
+      mFragmentEnd = end;
+    }
+    else {
+      start = -1.0;
+      end = -1.0;
+    }
+  }
+  if (start > 0.0) {
+    SetCurrentTime(start);
+    mFragmentStart = start;
+  }
+}
+
 void nsHTMLMediaElement::MetadataLoaded(PRUint32 aChannels, PRUint32 aRate)
 {
   mChannels = aChannels;
   mRate = aRate;
   ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_METADATA);
   DispatchAsyncEvent(NS_LITERAL_STRING("durationchange"));
   DispatchAsyncEvent(NS_LITERAL_STRING("loadedmetadata"));
+  if (mDecoder && mDecoder->IsSeekable()) {
+    ProcessMediaFragmentURI();
+    mDecoder->SetEndTime(mFragmentEnd);
+  }
 }
 
 void nsHTMLMediaElement::FirstFrameLoaded(PRBool aResourceFullyLoaded)
 {
   ChangeReadyState(aResourceFullyLoaded ?
     nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA :
     nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA);
   ChangeDelayLoadStatus(PR_FALSE);
@@ -2639,9 +2658,56 @@ void nsHTMLMediaElement::FireTimeUpdate(
   if (!aPeriodic ||
       (mLastCurrentTime != time &&
        (mTimeUpdateTime.IsNull() ||
         now - mTimeUpdateTime >= TimeDuration::FromMilliseconds(TIMEUPDATE_MS)))) {
     DispatchAsyncEvent(NS_LITERAL_STRING("timeupdate"));
     mTimeUpdateTime = now;
     mLastCurrentTime = time;
   }
+  if (mFragmentEnd >= 0.0 && time >= mFragmentEnd) {
+    Pause();
+    mFragmentEnd = -1.0;
+    mFragmentStart = -1.0;
+    mDecoder->SetEndTime(mFragmentEnd);
+  }
 }
+
+void nsHTMLMediaElement::GetCurrentSpec(nsCString& aString)
+{
+  if (mDecoder) {
+    nsMediaStream* stream = mDecoder->GetCurrentStream();
+    if (stream) {
+      stream->URI()->GetSpec(aString);
+    }
+  } else if (mLoadingSrc) {
+    mLoadingSrc->GetSpec(aString);
+  }
+}
+
+/* attribute double initialTime; */
+NS_IMETHODIMP nsHTMLMediaElement::GetInitialTime(double *aTime)
+{
+  // If there is no start fragment then the initalTime is zero.
+  // Clamp to duration if it is greater than duration.
+  double duration = 0.0;
+  nsresult rv = GetDuration(&duration);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  *aTime = mFragmentStart < 0.0 ? 0.0 : mFragmentStart;
+  if (*aTime > duration) {
+    *aTime = duration;
+  }
+  return NS_OK;
+}
+
+/* attribute double mozFragmentEnd; */
+NS_IMETHODIMP nsHTMLMediaElement::GetMozFragmentEnd(double *aTime)
+{
+  double duration = 0.0;
+  nsresult rv = GetDuration(&duration);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // If there is no end fragment, or the fragment end is greater than the
+  // duration, return the duration.
+  *aTime = (mFragmentEnd < 0.0 || mFragmentEnd > duration) ? duration : mFragmentEnd;
+  return NS_OK;
+}
new file mode 100644
--- /dev/null
+++ b/content/html/content/src/nsMediaFragmentURIParser.cpp
@@ -0,0 +1,339 @@
+/* -*- 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 the media fragment URI parser.
+ *
+ * The Initial Developer of the Original Code is the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Chris Double <chris.double@double.co.nz>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+#include "nsCharSeparatedTokenizer.h"
+#include "nsEscape.h"
+#include "nsMediaFragmentURIParser.h"
+
+nsMediaFragmentURIParser::nsMediaFragmentURIParser(const nsCString& aSpec)
+{
+  nsReadingIterator<char> start, end;
+  aSpec.BeginReading(start);
+  aSpec.EndReading(end);
+  if (FindCharInReadable('#', start, end)) {
+    mHash = Substring(++start, end);
+  }
+}
+
+PRBool nsMediaFragmentURIParser::ParseNPT(nsDependentSubstring& aString, double& aStart, double& aEnd)
+{
+  nsDependentSubstring original(aString);
+  if (aString.Length() > 4 &&
+      aString[0] == 'n' && aString[1] == 'p' &&
+      aString[2] == 't' && aString[3] == ':') {
+    aString.Rebind(aString, 4);
+  }
+
+  if (aString.Length() == 0) {
+    return PR_FALSE;
+  }
+
+  double start = -1.0;
+  double end = -1.0;
+
+  if (ParseNPTTime(aString, start)) {
+    aStart = start;
+  }
+
+  if (aString.Length() == 0) {
+    return PR_TRUE;
+  }
+
+  if (aString[0] != ',') {
+    aString.Rebind(original, 0);
+    return PR_FALSE;
+  }
+
+  aString.Rebind(aString, 1);
+
+  if (aString.Length() == 0) {
+    aString.Rebind(original, 0);
+    return PR_FALSE;
+  }
+
+  if (ParseNPTTime(aString, end)) {
+    aEnd = end;
+  }
+
+  if (aString.Length() != 0) {
+    aString.Rebind(original, 0);
+    return PR_FALSE;
+  }
+
+  return PR_TRUE;
+}
+
+PRBool nsMediaFragmentURIParser::ParseNPTTime(nsDependentSubstring& aString, double& aTime)
+{
+  if (aString.Length() == 0) {
+    return PR_FALSE;
+  }
+
+  return
+    ParseNPTHHMMSS(aString, aTime) ||
+    ParseNPTMMSS(aString, aTime) ||
+    ParseNPTSec(aString, aTime);
+}
+
+// Return PR_TRUE if the given character is a numeric character
+static PRBool IsDigit(nsDependentSubstring::char_type aChar)
+{
+  return (aChar >= '0' && aChar <= '9');
+}
+
+// Return the index of the first character in the string that is not
+// a numerical digit, starting from 'aStart'.
+static PRUint32 FirstNonDigit(nsDependentSubstring& aString, PRUint32 aStart)
+{
+   while (aStart < aString.Length() && IsDigit(aString[aStart])) {
+    ++aStart;
+  }
+  return aStart;
+}
+ 
+PRBool nsMediaFragmentURIParser::ParseNPTSec(nsDependentSubstring& aString, double& aSec)
+{
+  nsDependentSubstring original(aString);
+  if (aString.Length() == 0) {
+    return PR_FALSE;
+  }
+
+  PRUint32 index = FirstNonDigit(aString, 0);
+  if (index == 0) {
+    return PR_FALSE;
+  }
+
+  nsDependentSubstring n(aString, 0, index);
+  PRInt32 ec;
+  PRInt32 s = PromiseFlatString(n).ToInteger(&ec);
+  if (NS_FAILED(ec)) {
+    return PR_FALSE;
+  }
+
+  aString.Rebind(aString, index);
+  double fraction = 0.0;
+  if (!ParseNPTFraction(aString, fraction)) {
+    aString.Rebind(original, 0);
+    return PR_FALSE;
+  }
+
+  aSec = s + fraction;
+  return PR_TRUE;
+}
+
+PRBool nsMediaFragmentURIParser::ParseNPTMMSS(nsDependentSubstring& aString, double& aTime)
+{
+  nsDependentSubstring original(aString);
+  PRUint32 mm = 0;
+  PRUint32 ss = 0;
+  double fraction = 0.0;
+  if (!ParseNPTMM(aString, mm)) {
+    aString.Rebind(original, 0);
+    return PR_FALSE;
+  }
+
+  if (aString.Length() < 2 || aString[0] != ':') {
+    aString.Rebind(original, 0);
+    return PR_FALSE;
+  }
+
+  aString.Rebind(aString, 1);
+  if (!ParseNPTSS(aString, ss)) {
+    aString.Rebind(original, 0);
+    return PR_FALSE;
+  }
+
+  if (!ParseNPTFraction(aString, fraction)) {
+    aString.Rebind(original, 0);
+    return PR_FALSE;
+  }
+  aTime = mm * 60 + ss + fraction;
+  return PR_TRUE;
+}
+
+PRBool nsMediaFragmentURIParser::ParseNPTFraction(nsDependentSubstring& aString, double& aFraction)
+{
+  double fraction = 0.0;
+
+  if (aString.Length() > 0 && aString[0] == '.') {
+    PRUint32 index = FirstNonDigit(aString, 1);
+
+    if (index > 1) {
+      nsDependentSubstring number(aString, 0, index);
+      PRInt32 ec;
+      fraction = PromiseFlatString(number).ToDouble(&ec);
+      if (NS_FAILED(ec)) {
+        return PR_FALSE;
+      }
+    }
+    aString.Rebind(aString, index);
+  }
+
+  aFraction = fraction;
+  return PR_TRUE;
+}
+
+PRBool nsMediaFragmentURIParser::ParseNPTHHMMSS(nsDependentSubstring& aString, double& aTime)
+{
+  nsDependentSubstring original(aString);
+  PRUint32 hh = 0;
+  double seconds = 0.0;
+  if (!ParseNPTHH(aString, hh)) {
+    return PR_FALSE;
+  }
+
+  if (aString.Length() < 2 || aString[0] != ':') {
+    aString.Rebind(original, 0);
+    return PR_FALSE;
+  }
+
+  aString.Rebind(aString, 1);
+  if (!ParseNPTMMSS(aString, seconds)) {
+    aString.Rebind(original, 0);
+    return PR_FALSE;
+  }
+
+  aTime = hh * 3600 + seconds;
+  return PR_TRUE;
+}
+
+PRBool nsMediaFragmentURIParser::ParseNPTHH(nsDependentSubstring& aString, PRUint32& aHour)
+{
+  if (aString.Length() == 0) {
+    return PR_FALSE;
+  }
+
+  PRUint32 index = FirstNonDigit(aString, 0);
+  if (index == 0) {
+    return PR_FALSE;
+  }
+
+  nsDependentSubstring n(aString, 0, index);
+  PRInt32 ec;
+  PRInt32 u = PromiseFlatString(n).ToInteger(&ec);
+  if (NS_FAILED(ec)) {
+    return PR_FALSE;
+  }
+
+  aString.Rebind(aString, index);
+  aHour = u;
+  return PR_TRUE;
+}
+
+PRBool nsMediaFragmentURIParser::ParseNPTMM(nsDependentSubstring& aString, PRUint32& aMinute)
+{
+  return ParseNPTSS(aString, aMinute);
+}
+
+PRBool nsMediaFragmentURIParser::ParseNPTSS(nsDependentSubstring& aString, PRUint32& aSecond)
+{
+  if (aString.Length() < 2) {
+    return PR_FALSE;
+  }
+
+  if (IsDigit(aString[0]) && IsDigit(aString[1])) {
+    nsDependentSubstring n(aString, 0, 2);
+    PRInt32 ec;
+
+    PRInt32 u = PromiseFlatString(n).ToInteger(&ec);
+    if (NS_FAILED(ec)) {
+      return PR_FALSE;
+    }
+
+    aString.Rebind(aString, 2);
+    if (u >= 60)
+      return PR_FALSE;
+
+    aSecond = u;
+    return PR_TRUE;
+  }
+
+  return PR_FALSE;
+}
+
+void nsMediaFragmentURIParser::Parse()
+{
+  nsCCharSeparatedTokenizer tokenizer(mHash, '&');
+  while (tokenizer.hasMoreTokens()) {
+    const nsCSubstring& nv = tokenizer.nextToken();
+    PRInt32 index = nv.FindChar('=');
+    if (index >= 0) {
+      nsCAutoString name;
+      nsCAutoString value;
+      NS_UnescapeURL(StringHead(nv, index), esc_Ref | esc_AlwaysCopy, name);
+      NS_UnescapeURL(Substring(nv, index + 1, nv.Length()),
+                     esc_Ref | esc_AlwaysCopy, value);
+      nsAutoString a = NS_ConvertUTF8toUTF16(name);
+      nsAutoString b = NS_ConvertUTF8toUTF16(value);
+      mFragments.AppendElement(Pair(a, b));
+    }
+  }
+}
+
+double nsMediaFragmentURIParser::GetStartTime()
+{
+  for (PRUint32 i = 0; i < mFragments.Length(); ++i) {
+    PRUint32 index = mFragments.Length() - i - 1;
+    if (mFragments[index].mName.EqualsLiteral("t")) {
+      double start = -1;
+      double end = -1;
+      nsDependentSubstring s(mFragments[index].mValue, 0);
+      if (ParseNPT(s, start, end)) {
+        return start;
+      }
+    }
+  }
+  return 0.0;
+}
+
+double nsMediaFragmentURIParser::GetEndTime()
+{
+  for (PRUint32 i = 0; i < mFragments.Length(); ++i) {
+    PRUint32 index = mFragments.Length() - i - 1;
+    if (mFragments[index].mName.EqualsLiteral("t")) {
+      double start = -1;
+      double end = -1;
+      nsDependentSubstring s(mFragments[index].mValue, 0);
+      if (ParseNPT(s, start, end)) {
+        return end;
+      }
+    }
+  }
+  return -1;
+}
+
+
new file mode 100644
--- /dev/null
+++ b/content/html/content/src/nsMediaFragmentURIParser.h
@@ -0,0 +1,107 @@
+/* -*- 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 the media fragment URI parser.
+ *
+ * The Initial Developer of the Original Code is the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Chris Double <chris.double@double.co.nz>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+#if !defined(nsMediaFragmentURIParser_h__)
+#define nsMediaFragmentURIParser_h__
+
+#include "nsString.h"
+#include "nsTArray.h"
+
+// Class to handle parsing of a W3C media fragment URI as per
+// spec at: http://www.w3.org/TR/media-frags/
+// Only the temporaral URI portion of the spec is implemented.
+// To use:
+// a) Construct an instance with the URI containing the fragment
+// b) Call Parse() method to parse the fragment
+// c) Use GetStartTime() and GetEndTime() to get the start/end
+//    times from any temporal fragment included in the URI.
+class nsMediaFragmentURIParser
+{
+  struct Pair
+  {
+    Pair(const nsAString& aName, const nsAString& aValue) :
+      mName(aName), mValue(aValue) { }
+
+    nsString mName;
+    nsString mValue;
+  };
+
+public:
+  // Create a parser, with the URL including fragment identifier
+  // in 'aSpec'.
+  nsMediaFragmentURIParser(const nsCString& aSpec);
+
+  // Parse the URI fragment included in the URI that was passed
+  // on construction.
+  void Parse();
+
+  // Return the start time in seconds obtained from the URI
+  // fragment. If no start time or no valid temporal fragment
+  // exists then 0 is returned.
+  double GetStartTime();
+
+  // Return the end time in seconds obtained from the URI
+  // fragment. If no end time or no valid temporal fragment
+  // exists then -1 is returned.
+  double GetEndTime();
+
+private:
+  // The following methods parse the fragment as per the media
+  // fragments specification. 'aString' contains the remaining
+  // fragment data to be parsed. The method returns PR_TRUE
+  // if the parse was successful and leaves the remaining unparsed
+  // data in 'aString'. If the parse fails then PR_FALSE is returned
+  // and 'aString' is left as it was when called.
+  PRBool ParseNPT(nsDependentSubstring& aString, double& aStart, double& aEnd);
+  PRBool ParseNPTTime(nsDependentSubstring& aString, double& aTime);
+  PRBool ParseNPTSec(nsDependentSubstring& aString, double& aSec);
+  PRBool ParseNPTFraction(nsDependentSubstring& aString, double& aFraction);
+  PRBool ParseNPTMMSS(nsDependentSubstring& aString, double& aTime);
+  PRBool ParseNPTHHMMSS(nsDependentSubstring& aString, double& aTime);
+  PRBool ParseNPTHH(nsDependentSubstring& aString, PRUint32& aHour);
+  PRBool ParseNPTMM(nsDependentSubstring& aString, PRUint32& aMinute);
+  PRBool ParseNPTSS(nsDependentSubstring& aString, PRUint32& aSecond);
+
+  // Fragment portion of the URI given on construction
+  nsCAutoString mHash;
+
+  // An array of name/value pairs containing the media fragments
+  // parsed from the URI.
+  nsTArray<Pair> mFragments;
+};
+
+#endif
--- a/content/media/nsBuiltinDecoder.cpp
+++ b/content/media/nsBuiltinDecoder.cpp
@@ -934,16 +934,25 @@ nsresult nsBuiltinDecoder::GetSeekable(n
                               : initialTime + GetDuration();
     aSeekable->Add(initialTime, end);
     return NS_OK;
   }
 
   return GetBuffered(aSeekable);
 }
 
+void nsBuiltinDecoder::SetEndTime(double aTime)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
+  if (mDecoderStateMachine) {
+    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+    mDecoderStateMachine->SetFragmentEndTime(static_cast<PRInt64>(aTime * USECS_PER_S));
+  }
+}
+
 void nsBuiltinDecoder::Suspend()
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   if (mStream) {
     mStream->Suspend(PR_TRUE);
   }
 }
 
--- a/content/media/nsBuiltinDecoder.h
+++ b/content/media/nsBuiltinDecoder.h
@@ -276,16 +276,19 @@ public:
   // aDuration is in microseconds.
   virtual void SetDuration(PRInt64 aDuration) = 0;
 
   // Called while decoding metadata to set the end time of the media
   // resource. The decoder monitor must be obtained before calling this.
   // aEndTime is in microseconds.
   virtual void SetEndTime(PRInt64 aEndTime) = 0;
 
+  // Set the media fragment end time. aEndTime is in microseconds.
+  virtual void SetFragmentEndTime(PRInt64 aEndTime) = 0;
+
   // Functions used by assertions to ensure we're calling things
   // on the appropriate threads.
   virtual PRBool OnDecodeThread() const = 0;
 
   // Returns PR_TRUE if the current thread is the state machine thread.
   virtual PRBool OnStateMachineThread() const = 0;
 
   virtual nsHTMLMediaElement::NextFrameStatus GetNextFrameStatus() = 0;
@@ -432,16 +435,20 @@ class nsBuiltinDecoder : public nsMediaD
   // Set a flag indicating whether seeking is supported
   virtual void SetSeekable(PRBool aSeekable);
 
   // Return PR_TRUE if seeking is supported.
   virtual PRBool IsSeekable();
 
   virtual nsresult GetSeekable(nsTimeRanges* aSeekable);
 
+  // Set the end time of the media resource. When playback reaches
+  // this point the media pauses. aTime is in seconds.
+  virtual void SetEndTime(double aTime);
+
   virtual Statistics GetStatistics();
 
   // 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
--- a/content/media/nsBuiltinDecoderStateMachine.cpp
+++ b/content/media/nsBuiltinDecoderStateMachine.cpp
@@ -197,16 +197,17 @@ nsBuiltinDecoderStateMachine::nsBuiltinD
                                                            nsBuiltinDecoderReader* aReader) :
   mDecoder(aDecoder),
   mState(DECODER_STATE_DECODING_METADATA),
   mCbCrSize(0),
   mPlayDuration(0),
   mStartTime(-1),
   mEndTime(-1),
   mSeekTime(0),
+  mFragmentEndTime(-1),
   mReader(aReader),
   mCurrentFrameTime(0),
   mAudioStartTime(-1),
   mAudioEndTime(-1),
   mVideoFrameEndTime(-1),
   mVolume(1.0),
   mSeekable(PR_TRUE),
   mPositionChangeQueued(PR_FALSE),
@@ -848,25 +849,30 @@ void nsBuiltinDecoderStateMachine::Updat
     NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
   }
 }
 
 void nsBuiltinDecoderStateMachine::UpdatePlaybackPosition(PRInt64 aTime)
 {
   UpdatePlaybackPositionInternal(aTime);
 
-  if (!mPositionChangeQueued) {
+  PRBool fragmentEnded = mFragmentEndTime >= 0 && GetMediaTime() >= mFragmentEndTime;
+  if (!mPositionChangeQueued || fragmentEnded) {
     mPositionChangeQueued = PR_TRUE;
     nsCOMPtr<nsIRunnable> event =
       NS_NewRunnableMethod(mDecoder, &nsBuiltinDecoder::PlaybackPositionChanged);
     NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
   }
 
   // Notify DOM of any queued up audioavailable events
   mEventManager.DispatchPendingEvents(GetMediaTime());
+
+  if (fragmentEnded) {
+    StopPlayback();
+  }
 }
 
 void nsBuiltinDecoderStateMachine::ClearPositionChangeFlag()
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
 
   mPositionChangeQueued = PR_FALSE;
@@ -930,16 +936,23 @@ void nsBuiltinDecoderStateMachine::SetDu
 void nsBuiltinDecoderStateMachine::SetEndTime(PRInt64 aEndTime)
 {
   NS_ASSERTION(OnDecodeThread(), "Should be on decode thread");
   mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
 
   mEndTime = aEndTime;
 }
 
+void nsBuiltinDecoderStateMachine::SetFragmentEndTime(PRInt64 aEndTime)
+{
+  mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
+
+  mFragmentEndTime = aEndTime < 0 ? aEndTime : aEndTime + mStartTime;
+}
+
 void nsBuiltinDecoderStateMachine::SetSeekable(PRBool aSeekable)
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
 
   mSeekable = aSeekable;
 }
 
@@ -1701,17 +1714,17 @@ void nsBuiltinDecoderStateMachine::Advan
     }
     StartBuffering();
     ScheduleStateMachine();
     return;
   }
 
   // We've got enough data to keep playing until at least the next frame.
   // Start playing now if need be.
-  if (!IsPlaying()) {
+  if (!IsPlaying() && ((mFragmentEndTime >= 0 && clock_time < mFragmentEndTime) || mFragmentEndTime < 0)) {
     StartPlayback();
   }
 
   if (currentFrame) {
     // Decode one frame and display it.
     TimeStamp presTime = mPlayStartTime - UsecsToDuration(mPlayDuration) +
                           UsecsToDuration(currentFrame->mTime - mStartTime);
     NS_ASSERTION(currentFrame->mTime >= mStartTime, "Should have positive frame time");
--- a/content/media/nsBuiltinDecoderStateMachine.h
+++ b/content/media/nsBuiltinDecoderStateMachine.h
@@ -273,16 +273,19 @@ public:
   // Schedules the shared state machine thread to run the state machine
   // in aUsecs microseconds from now, if it's not already scheduled to run
   // earlier, in which case the request is discarded.
   nsresult ScheduleStateMachine(PRInt64 aUsecs);
 
   // Timer function to implement ScheduleStateMachine(aUsecs).
   void TimeoutExpired();
 
+  // Set the media fragment end time. aEndTime is in microseconds.
+  void SetFragmentEndTime(PRInt64 aEndTime);
+
 protected:
 
   // Returns PR_TRUE if we've got less than aAudioUsecs microseconds of decoded
   // and playable data. The decoder monitor must be held.
   PRBool HasLowDecodedData(PRInt64 aAudioUsecs) const;
 
   // Returns PR_TRUE if we're running low on data which is not yet decoded.
   // The decoder monitor must be held.
@@ -506,16 +509,19 @@ protected:
   // machine, decode, and main threads. Access controlled by decoder monitor.
   PRInt64 mEndTime;
 
   // Position to seek to in microseconds when the seek state transition occurs.
   // The decoder monitor lock must be obtained before reading or writing
   // this value. Accessed on main and decode thread.
   PRInt64 mSeekTime;
 
+  // Media Fragment end time in microseconds. Access controlled by decoder monitor.
+  PRInt64 mFragmentEndTime;
+
   // The audio stream resource. Used on the state machine, and audio threads.
   // This is created and destroyed on the audio thread, while holding the
   // decoder monitor, so if this is used off the audio thread, you must
   // first acquire the decoder monitor and check that it is non-null.
   nsRefPtr<nsAudioStream> mAudioStream;
 
   // The reader, don't call its methods with the decoder monitor held.
   // This is created in the play state machine's constructor, and destroyed
--- a/content/media/nsMediaDecoder.h
+++ b/content/media/nsMediaDecoder.h
@@ -291,16 +291,20 @@ public:
   virtual void SetSeekable(PRBool aSeekable) = 0;
 
   // Return PR_TRUE if seeking is supported.
   virtual PRBool IsSeekable() = 0;
 
   // Return the time ranges that can be seeked into.
   virtual nsresult GetSeekable(nsTimeRanges* aSeekable) = 0;
 
+  // Set the end time of the media resource. When playback reaches
+  // this point the media pauses. aTime is in seconds.
+  virtual void SetEndTime(double aTime) = 0;
+
   // Invalidate the frame.
   virtual void Invalidate();
 
   // Fire progress events if needed according to the time and byte
   // constraints outlined in the specification. aTimer is PR_TRUE
   // if the method is called as a result of the progress timer rather
   // than the result of downloaded data.
   virtual void Progress(PRBool aTimer);
--- a/dom/interfaces/html/nsIDOMHTMLMediaElement.idl
+++ b/dom/interfaces/html/nsIDOMHTMLMediaElement.idl
@@ -52,17 +52,17 @@
 
 // undef the GetCurrentTime macro defined in WinBase.h from the MS Platform SDK
 %{C++
 #ifdef GetCurrentTime
 #undef GetCurrentTime
 #endif
 %}
 
-[scriptable, uuid(9441effd-fbe1-4f48-aa90-1741c1e390a1)]
+[scriptable, uuid(16b81ded-30cb-427d-831d-1635dc68d346)]
 interface nsIDOMHTMLMediaElement : nsIDOMHTMLElement
 {
   // error state
   readonly attribute nsIDOMMediaError error;
 
   // network state
            attribute DOMString src;
   readonly attribute DOMString currentSrc;
@@ -82,16 +82,17 @@ interface nsIDOMHTMLMediaElement : nsIDO
   const unsigned short HAVE_CURRENT_DATA = 2;
   const unsigned short HAVE_FUTURE_DATA = 3;
   const unsigned short HAVE_ENOUGH_DATA = 4;
   readonly attribute unsigned short readyState;
   readonly attribute boolean seeking;
 
   // playback state
            attribute double currentTime;
+  readonly attribute double initialTime;
   readonly attribute double duration;
   readonly attribute boolean paused;
   readonly attribute nsIDOMTimeRanges seekable;
   readonly attribute boolean ended;
   readonly attribute boolean mozAutoplayEnabled;
            attribute boolean autoplay;
   void play();
   void pause();
@@ -113,9 +114,14 @@ interface nsIDOMHTMLMediaElement : nsIDO
 
   // Mozilla extension: load data from another media element. This is like
   // load() but we don't run the resource selection algorithm; instead
   // we just set our source to other's currentSrc. This is optimized
   // so that this element will get access to all of other's cached/
   // buffered data. In fact any future data downloaded by this element or
   // other will be sharable by both elements.
   void mozLoadFrom(in nsIDOMHTMLMediaElement other);
+
+  // Mozilla extension: provides access to the fragment end time if
+  // the media element has a fragment URI for the currentSrc, otherwise
+  // it is equal to the media duration.
+  readonly attribute double mozFragmentEnd;
 };