diff --git a/content/media/video/public/nsMediaDecoder.h b/content/media/video/public/nsMediaDecoder.h index 80b1a8bf700..ccea2b9cd7f 100644 --- a/content/media/video/public/nsMediaDecoder.h +++ b/content/media/video/public/nsMediaDecoder.h @@ -177,6 +177,11 @@ class nsMediaDecoder : public nsIObserver // Set the size of the video file in bytes. virtual void SetTotalBytes(PRInt64 aBytes) = 0; + // Set the duration of the media resource in units of milliseconds. + // This is called via a channel listener if it can pick up the duration + // from a content header. Must be called from the main thread only. + virtual void SetDuration(PRInt64 aDuration) = 0; + // Set a flag indicating whether seeking is supported virtual void SetSeekable(PRBool aSeekable) = 0; diff --git a/content/media/video/public/nsOggDecoder.h b/content/media/video/public/nsOggDecoder.h index e41349211d8..e618d2fa4a5 100644 --- a/content/media/video/public/nsOggDecoder.h +++ b/content/media/video/public/nsOggDecoder.h @@ -349,6 +349,11 @@ class nsOggDecoder : public nsMediaDecoder // Get the size of the media file in bytes. Called on the main thread only. virtual void SetTotalBytes(PRInt64 aBytes); + // Set the duration of the media resource in units of milliseconds. + // This is called via a channel listener if it can pick up the duration + // from a content header. Must be called from the main thread only. + virtual void SetDuration(PRInt64 aDuration); + // Set a flag indicating whether seeking is supported virtual void SetSeekable(PRBool aSeekable); diff --git a/content/media/video/public/nsWaveDecoder.h b/content/media/video/public/nsWaveDecoder.h index ec2c5d44eef..f0a5e0c96ac 100644 --- a/content/media/video/public/nsWaveDecoder.h +++ b/content/media/video/public/nsWaveDecoder.h @@ -200,6 +200,10 @@ class nsWaveDecoder : public nsMediaDecoder virtual void SetTotalBytes(PRInt64 aBytes); + // 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(); diff --git a/content/media/video/src/nsChannelToPipeListener.cpp b/content/media/video/src/nsChannelToPipeListener.cpp index 1c8671d6f06..202d22761e3 100644 --- a/content/media/video/src/nsChannelToPipeListener.cpp +++ b/content/media/video/src/nsChannelToPipeListener.cpp @@ -110,6 +110,28 @@ nsresult nsChannelToPipeListener::OnStartRequest(nsIRequest* aRequest, nsISuppor ranges); PRBool acceptsRanges = ranges.EqualsLiteral("bytes"); + if (!mSeeking) { + // Look for duration headers from known Ogg content systems. In the case + // of multiple options for obtaining the duration the order of precedence is; + // 1) The Media resource metadata if possible (done by the decoder itself). + // 2) X-Content-Duration. + // 3) x-amz-meta-content-duration. + // 4) Perform a seek in the decoder to find the value. + nsCAutoString durationText; + PRInt32 ec = 0; + nsresult rv = hc->GetResponseHeader(NS_LITERAL_CSTRING("X-Content-Duration"), durationText); + if (NS_FAILED(rv)) { + rv = hc->GetResponseHeader(NS_LITERAL_CSTRING("X-AMZ-Meta-Content-Duration"), durationText); + } + + if (NS_SUCCEEDED(rv)) { + float duration = durationText.ToFloat(&ec); + if (ec == NS_OK && duration >= 0) { + mDecoder->SetDuration(PRInt64(duration*1000)); + } + } + } + PRUint32 responseStatus = 0; hc->GetResponseStatus(&responseStatus); if (mSeeking && responseStatus == HTTP_OK_CODE) { diff --git a/content/media/video/src/nsOggDecoder.cpp b/content/media/video/src/nsOggDecoder.cpp index 6f92fb574f9..ff533ff3e62 100644 --- a/content/media/video/src/nsOggDecoder.cpp +++ b/content/media/video/src/nsOggDecoder.cpp @@ -286,6 +286,11 @@ public: // resource. The decoder monitor must be obtained before calling this. void SetContentLength(PRInt64 aLength); + // Called from the main thread to set the duration of the media resource + // if it is able to be obtained via HTTP headers. The decoder monitor + // must be obtained before calling this. + void SetDuration(PRInt64 aDuration); + // Called from the main thread to set whether the media resource can // be seeked. The decoder monitor must be obtained before calling this. void SetSeekable(PRBool aSeekable); @@ -839,6 +844,12 @@ void nsOggDecodeStateMachine::SetContentLength(PRInt64 aLength) mContentLength = aLength; } +void nsOggDecodeStateMachine::SetDuration(PRInt64 aDuration) +{ + // NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "SetDuration() called without acquiring decoder monitor"); + mDuration = aDuration; +} + void nsOggDecodeStateMachine::SetSeekable(PRBool aSeekable) { // NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "SetSeekable() called without acquiring decoder monitor"); @@ -1188,12 +1199,14 @@ void nsOggDecodeStateMachine::LoadOggHeaders() // Get the duration from the Ogg file. We only do this if the // content length of the resource is known as we need to seek // to the end of the file to get the last time field. We also - // only do this if the resource is seekable. + // only do this if the resource is seekable and if we haven't + // already obtained the duration via an HTTP header. { nsAutoMonitor mon(mDecoder->GetMonitor()); if (mState != DECODER_STATE_SHUTDOWN && mContentLength >= 0 && - mSeekable) { + mSeekable && + mDuration == -1) { mDecoder->StopProgressUpdates(); // Don't hold the monitor during the duration // call as it can issue seek requests @@ -1916,6 +1929,15 @@ void nsOggDecoder::PlaybackPositionChanged() } } +void nsOggDecoder::SetDuration(PRInt64 aDuration) +{ + mDuration = aDuration; + if (mDecodeStateMachine) { + nsAutoMonitor mon(mMonitor); + mDecodeStateMachine->SetDuration(mDuration); + } +} + void nsOggDecoder::SetSeekable(PRBool aSeekable) { mSeekable = aSeekable; diff --git a/content/media/video/src/nsWaveDecoder.cpp b/content/media/video/src/nsWaveDecoder.cpp index 26772264eb5..f5d42718e2c 100644 --- a/content/media/video/src/nsWaveDecoder.cpp +++ b/content/media/video/src/nsWaveDecoder.cpp @@ -1574,6 +1574,13 @@ nsWaveDecoder::MediaErrorDecode() #endif } +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) { diff --git a/content/media/video/test/Makefile.in b/content/media/video/test/Makefile.in index b40b0db5795..256137aad02 100644 --- a/content/media/video/test/Makefile.in +++ b/content/media/video/test/Makefile.in @@ -65,6 +65,12 @@ _TEST_FILES += \ test_bug448534.html \ test_bug461281.html \ test_can_play_type_ogg.html \ + test_contentDuration1.html \ + test_contentDuration2.html \ + test_contentDuration3.html \ + test_contentDuration4.html \ + test_contentDuration5.html \ + test_contentDuration6.html \ test_duration1.html \ test_ended1.html \ test_ended2.html \ @@ -80,6 +86,12 @@ _TEST_FILES += \ 320x240.allow-origin.ogv^headers^ \ bug461281.ogg \ redirect.sjs \ + contentDuration1.sjs \ + contentDuration2.sjs \ + contentDuration3.sjs \ + contentDuration4.sjs \ + contentDuration5.sjs \ + contentDuration6.sjs \ seek.ogv \ $(NULL) diff --git a/content/media/video/test/contentDuration1.sjs b/content/media/video/test/contentDuration1.sjs new file mode 100644 index 00000000000..66b7be866ed --- /dev/null +++ b/content/media/video/test/contentDuration1.sjs @@ -0,0 +1,23 @@ +function handleRequest(request, response) +{ + var file = Components.classes["@mozilla.org/file/directory_service;1"]. + getService(Components.interfaces.nsIProperties). + get("CurWorkD", Components.interfaces.nsILocalFile); + var fis = Components.classes['@mozilla.org/network/file-input-stream;1']. + createInstance(Components.interfaces.nsIFileInputStream); + var bis = Components.classes["@mozilla.org/binaryinputstream;1"]. + createInstance(Components.interfaces.nsIBinaryInputStream); + var paths = "tests/content/media/video/test/320x240.ogv"; + var split = paths.split("/"); + for(var i = 0; i < split.length; ++i) { + file.append(split[i]); + } + fis.init(file, -1, -1, false); + bis.setInputStream(fis); + var bytes = bis.readBytes(bis.available()); + response.setHeader("X-Content-Duration", "0.233", false); + response.setHeader("Content-Length", ""+bytes.length, false); + response.setHeader("Content-Type", "video/ogg", false); + response.write(bytes, bytes.length); + bis.close(); +} diff --git a/content/media/video/test/contentDuration2.sjs b/content/media/video/test/contentDuration2.sjs new file mode 100644 index 00000000000..5225b342e14 --- /dev/null +++ b/content/media/video/test/contentDuration2.sjs @@ -0,0 +1,23 @@ +function handleRequest(request, response) +{ + var file = Components.classes["@mozilla.org/file/directory_service;1"]. + getService(Components.interfaces.nsIProperties). + get("CurWorkD", Components.interfaces.nsILocalFile); + var fis = Components.classes['@mozilla.org/network/file-input-stream;1']. + createInstance(Components.interfaces.nsIFileInputStream); + var bis = Components.classes["@mozilla.org/binaryinputstream;1"]. + createInstance(Components.interfaces.nsIBinaryInputStream); + var paths = "tests/content/media/video/test/320x240.ogv"; + var split = paths.split("/"); + for(var i = 0; i < split.length; ++i) { + file.append(split[i]); + } + fis.init(file, -1, -1, false); + bis.setInputStream(fis); + var bytes = bis.readBytes(bis.available()); + response.setHeader("x-amz-meta-content-duration", "0.233", false); + response.setHeader("Content-Length", ""+bytes.length, false); + response.setHeader("Content-Type", "video/ogg", false); + response.write(bytes, bytes.length); + bis.close(); +} diff --git a/content/media/video/test/contentDuration3.sjs b/content/media/video/test/contentDuration3.sjs new file mode 100644 index 00000000000..d578873edd9 --- /dev/null +++ b/content/media/video/test/contentDuration3.sjs @@ -0,0 +1,23 @@ +function handleRequest(request, response) +{ + var file = Components.classes["@mozilla.org/file/directory_service;1"]. + getService(Components.interfaces.nsIProperties). + get("CurWorkD", Components.interfaces.nsILocalFile); + var fis = Components.classes['@mozilla.org/network/file-input-stream;1']. + createInstance(Components.interfaces.nsIFileInputStream); + var bis = Components.classes["@mozilla.org/binaryinputstream;1"]. + createInstance(Components.interfaces.nsIBinaryInputStream); + var paths = "tests/content/media/video/test/320x240.ogv"; + var split = paths.split("/"); + for(var i = 0; i < split.length; ++i) { + file.append(split[i]); + } + fis.init(file, -1, -1, false); + bis.setInputStream(fis); + var bytes = bis.readBytes(bis.available()); + response.setStatusLine(request.httpVersion, 200, "Content Follows"); + response.setHeader("Content-Length", ""+bytes.length, false); + response.setHeader("Content-Type", "video/ogg", false); + response.write(bytes, bytes.length); + bis.close(); +} diff --git a/content/media/video/test/contentDuration4.sjs b/content/media/video/test/contentDuration4.sjs new file mode 100644 index 00000000000..1ed32b9a3af --- /dev/null +++ b/content/media/video/test/contentDuration4.sjs @@ -0,0 +1,24 @@ +function handleRequest(request, response) +{ + var file = Components.classes["@mozilla.org/file/directory_service;1"]. + getService(Components.interfaces.nsIProperties). + get("CurWorkD", Components.interfaces.nsILocalFile); + var fis = Components.classes['@mozilla.org/network/file-input-stream;1']. + createInstance(Components.interfaces.nsIFileInputStream); + var bis = Components.classes["@mozilla.org/binaryinputstream;1"]. + createInstance(Components.interfaces.nsIBinaryInputStream); + var paths = "tests/content/media/video/test/320x240.ogv"; + var split = paths.split("/"); + for(var i = 0; i < split.length; ++i) { + file.append(split[i]); + } + fis.init(file, -1, -1, false); + bis.setInputStream(fis); + var bytes = bis.readBytes(bis.available()); + response.setStatusLine(request.httpVersion, 200, "Content Follows"); + response.setHeader("X-Content-Duration", "-5", false); + response.setHeader("Content-Length", ""+bytes.length, false); + response.setHeader("Content-Type", "video/ogg", false); + response.write(bytes, bytes.length); + bis.close(); +} diff --git a/content/media/video/test/contentDuration5.sjs b/content/media/video/test/contentDuration5.sjs new file mode 100644 index 00000000000..3d6cf3b8026 --- /dev/null +++ b/content/media/video/test/contentDuration5.sjs @@ -0,0 +1,23 @@ +function handleRequest(request, response) +{ + var file = Components.classes["@mozilla.org/file/directory_service;1"]. + getService(Components.interfaces.nsIProperties). + get("CurWorkD", Components.interfaces.nsILocalFile); + var fis = Components.classes['@mozilla.org/network/file-input-stream;1']. + createInstance(Components.interfaces.nsIFileInputStream); + var bis = Components.classes["@mozilla.org/binaryinputstream;1"]. + createInstance(Components.interfaces.nsIBinaryInputStream); + var paths = "tests/content/media/video/test/320x240.ogv"; + var split = paths.split("/"); + for(var i = 0; i < split.length; ++i) { + file.append(split[i]); + } + fis.init(file, -1, -1, false); + bis.setInputStream(fis); + var bytes = bis.readBytes(bis.available()); + response.setStatusLine(request.httpVersion, 200, "Content Follows"); + response.setHeader("X-Content-Duration", "-6", false); + response.setHeader("Content-Length", ""+bytes.length, false); + response.setHeader("Content-Type", "video/ogg", false); + response.write(bytes, bytes.length); +} diff --git a/content/media/video/test/contentDuration6.sjs b/content/media/video/test/contentDuration6.sjs new file mode 100644 index 00000000000..9eacef6226e --- /dev/null +++ b/content/media/video/test/contentDuration6.sjs @@ -0,0 +1,24 @@ +function handleRequest(request, response) +{ + var file = Components.classes["@mozilla.org/file/directory_service;1"]. + getService(Components.interfaces.nsIProperties). + get("CurWorkD", Components.interfaces.nsILocalFile); + var fis = Components.classes['@mozilla.org/network/file-input-stream;1']. + createInstance(Components.interfaces.nsIFileInputStream); + var bis = Components.classes["@mozilla.org/binaryinputstream;1"]. + createInstance(Components.interfaces.nsIBinaryInputStream); + var paths = "tests/content/media/video/test/320x240.ogv"; + var split = paths.split("/"); + for(var i = 0; i < split.length; ++i) { + file.append(split[i]); + } + fis.init(file, -1, -1, false); + bis.setInputStream(fis); + var bytes = bis.readBytes(bis.available()); + response.setStatusLine(request.httpVersion, 200, "Content Follows"); + response.setHeader("X-Content-Duration", "Invalid Float Value", false); + response.setHeader("Content-Length", ""+bytes.length, false); + response.setHeader("Content-Type", "video/ogg", false); + response.write(bytes, bytes.length); + bis.close(); +} diff --git a/content/media/video/test/test_contentDuration1.html b/content/media/video/test/test_contentDuration1.html new file mode 100644 index 00000000000..60539f6a48f --- /dev/null +++ b/content/media/video/test/test_contentDuration1.html @@ -0,0 +1,27 @@ + + +
++ ++ + + diff --git a/content/media/video/test/test_contentDuration2.html b/content/media/video/test/test_contentDuration2.html new file mode 100644 index 00000000000..e8fc4ca80fe --- /dev/null +++ b/content/media/video/test/test_contentDuration2.html @@ -0,0 +1,27 @@ + + + +
+ ++ + + diff --git a/content/media/video/test/test_contentDuration3.html b/content/media/video/test/test_contentDuration3.html new file mode 100644 index 00000000000..9e975fa47a0 --- /dev/null +++ b/content/media/video/test/test_contentDuration3.html @@ -0,0 +1,27 @@ + + + +
+ ++ + + diff --git a/content/media/video/test/test_contentDuration4.html b/content/media/video/test/test_contentDuration4.html new file mode 100644 index 00000000000..27e10a7b50a --- /dev/null +++ b/content/media/video/test/test_contentDuration4.html @@ -0,0 +1,27 @@ + + + +
+ ++ + + diff --git a/content/media/video/test/test_contentDuration5.html b/content/media/video/test/test_contentDuration5.html new file mode 100644 index 00000000000..8304c0b0611 --- /dev/null +++ b/content/media/video/test/test_contentDuration5.html @@ -0,0 +1,27 @@ + + + +
+ ++ + + diff --git a/content/media/video/test/test_contentDuration6.html b/content/media/video/test/test_contentDuration6.html new file mode 100644 index 00000000000..f8c82499251 --- /dev/null +++ b/content/media/video/test/test_contentDuration6.html @@ -0,0 +1,27 @@ + + + +
+ ++ + +