Bug 449307 - Support duration attribute on media elements - r+sr=roc

This commit is contained in:
Chris Double 2008-11-04 21:08:39 +13:00
parent b3cdf58216
commit 1379f310fb
8 changed files with 200 additions and 12 deletions

View File

@ -67,7 +67,9 @@ class nsChannelToPipeListener : public nsIStreamListener
NS_DECL_NSISTREAMLISTENER
public:
nsChannelToPipeListener(nsMediaDecoder* aDecoder);
// If aSeeking is PR_TRUE then this listener was created as part of a
// seek request and is expecting a byte range partial result.
nsChannelToPipeListener(nsMediaDecoder* aDecoder, PRBool aSeeking = PR_FALSE);
nsresult Init();
nsresult GetInputStream(nsIInputStream** aStream);
void Stop();
@ -95,6 +97,9 @@ private:
// Total bytes transferred so far
PRInt64 mTotalBytes;
// PR_TRUE if this listener is expecting a byte range request result
PRPackedBool mSeeking;
};
#endif

View File

@ -142,6 +142,12 @@ class nsMediaDecoder : public nsIObserver
// Set the size of the video file in bytes.
virtual void SetTotalBytes(PRInt64 aBytes) = 0;
// Set a flag indicating whether seeking is supported
virtual void SetSeekable(PRBool aSeekable) = 0;
// Return PR_TRUE if seeking is supported.
virtual PRBool GetSeekable() = 0;
// Called when the HTML DOM element is bound.
virtual void ElementAvailable(nsHTMLMediaElement* anElement);

View File

@ -338,6 +338,12 @@ 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 a flag indicating whether seeking is supported
virtual void SetSeekable(PRBool aSeekable);
// Return PR_TRUE if seeking is supported.
virtual PRBool GetSeekable();
protected:
// Change to a new play state. This updates the mState variable and
// notifies any thread blocking on this objects monitor of the
@ -449,6 +455,10 @@ private:
// True if we are registered with the observer service for shutdown.
PRPackedBool mNotifyOnShutdown;
// True if the media resource is seekable (server supports byte range
// requests).
PRPackedBool mSeekable;
/******
* The following member variables can be accessed from any thread.
******/

View File

@ -42,11 +42,17 @@
#include "nsChannelToPipeListener.h"
#include "nsICachingChannel.h"
nsChannelToPipeListener::nsChannelToPipeListener(nsMediaDecoder* aDecoder) :
#define HTTP_OK_CODE 200
#define HTTP_PARTIAL_RESPONSE_CODE 206
nsChannelToPipeListener::nsChannelToPipeListener(
nsMediaDecoder* aDecoder,
PRBool aSeeking) :
mDecoder(aDecoder),
mIntervalStart(0),
mIntervalEnd(0),
mTotalBytes(0)
mTotalBytes(0),
mSeeking(aSeeking)
{
}
@ -98,10 +104,25 @@ nsresult nsChannelToPipeListener::OnStartRequest(nsIRequest* aRequest, nsISuppor
if (hc) {
PRUint32 responseStatus = 0;
hc->GetResponseStatus(&responseStatus);
if (responseStatus == 200) {
if (mSeeking && responseStatus == HTTP_OK_CODE) {
// If we get an OK response but we were seeking,
// and therefore expecting a partial response of
// HTTP_PARTIAL_RESPONSE_CODE, tell the decoder
// we don't support seeking.
mDecoder->SetSeekable(PR_FALSE);
}
else if (!mSeeking &&
(responseStatus == HTTP_OK_CODE ||
responseStatus == HTTP_PARTIAL_RESPONSE_CODE)) {
// We weren't seeking and got a valid response status,
// set the length of the content.
PRInt32 cl = 0;
hc->GetContentLength(&cl);
mDecoder->SetTotalBytes(cl);
// If we get an HTTP_OK_CODE response to our byte range
// request, then we don't support seeking.
mDecoder->SetSeekable(responseStatus == HTTP_PARTIAL_RESPONSE_CODE);
}
}

View File

@ -425,6 +425,16 @@ nsresult nsHttpStreamStrategy::Open(nsIStreamListener **aStreamListener)
*aStreamListener = mListener;
NS_ADDREF(*aStreamListener);
} else {
// Use a byte range request from the start of the resource.
// This enables us to detect if the stream supports byte range
// requests, and therefore seeking, early.
nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(mChannel);
if (hc) {
hc->SetRequestHeader(NS_LITERAL_CSTRING("Range"),
NS_LITERAL_CSTRING("bytes=0-"),
PR_FALSE);
}
rv = mChannel->AsyncOpen(mListener, nsnull);
NS_ENSURE_SUCCESS(rv, rv);
}
@ -525,7 +535,7 @@ public:
hc->SetRequestHeader(NS_LITERAL_CSTRING("Range"), rangeString, PR_FALSE);
}
mListener = new nsChannelToPipeListener(mDecoder);
mListener = new nsChannelToPipeListener(mDecoder, PR_TRUE);
NS_ENSURE_TRUE(mListener, NS_ERROR_OUT_OF_MEMORY);
mResult = mListener->Init();

View File

@ -35,6 +35,7 @@
* 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 "nsIFrame.h"
@ -267,6 +268,18 @@ public:
// monitor must be obtained before calling this.
float GetCurrentTime();
// Called from the main thread to get the duration. The decoder monitor
// must be obtained before calling this. It is in units of milliseconds.
PRInt64 GetDuration();
// Called from the main thread to set the content length of the media
// resource. The decoder monitor must be obtained before calling this.
void SetContentLength(PRInt64 aLength);
// 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);
// Get and set the audio volume. The decoder monitor must be
// obtained before calling this.
float GetVolume();
@ -427,6 +440,20 @@ private:
// monitor.
float mVolume;
// Duration of the media resource. It is accessed from the decoder and main
// threads. Synchronised via decoder monitor. It is in units of
// milliseconds.
PRInt64 mDuration;
// Content Length of the media resource if known. If it is -1 then the
// size is unknown. Accessed from the decoder and main threads. Synchronised
// via decoder monitor.
PRInt64 mContentLength;
// PR_TRUE if the media resource can be seeked. Accessed from the decoder
// and main threads. Synchronised via decoder monitor.
PRPackedBool mSeekable;
// PR_TRUE if an event to notify about a change in the playback
// position has been queued, but not yet run. It is set to PR_FALSE when
// the event is run. This allows coalescing of these events as they can be
@ -455,6 +482,9 @@ nsOggDecodeStateMachine::nsOggDecodeStateMachine(nsOggDecoder* aDecoder, nsChann
mSeekTime(0.0),
mCurrentFrameTime(0.0),
mVolume(1.0),
mDuration(-1),
mContentLength(-1),
mSeekable(PR_TRUE),
mPositionChangeQueued(PR_FALSE)
{
}
@ -773,6 +803,23 @@ float nsOggDecodeStateMachine::GetCurrentTime()
return mCurrentFrameTime;
}
PRInt64 nsOggDecodeStateMachine::GetDuration()
{
// NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "GetDuration() called without acquiring decoder monitor");
return mDuration;
}
void nsOggDecodeStateMachine::SetContentLength(PRInt64 aLength)
{
// NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "SetContentLength() called without acquiring decoder monitor");
mContentLength = aLength;
}
void nsOggDecodeStateMachine::SetSeekable(PRBool aSeekable)
{
// NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "SetSeekable() called without acquiring decoder monitor");
mSeekable = aSeekable;
}
void nsOggDecodeStateMachine::Shutdown()
{
@ -1044,12 +1091,12 @@ void nsOggDecodeStateMachine::LoadOggHeaders()
oggplay_get_audio_channels(mPlayer, i, &mAudioChannels);
LOG(PR_LOG_DEBUG, ("samplerate: %d, channels: %d", mAudioRate, mAudioChannels));
}
if (oggplay_set_track_active(mPlayer, i) < 0) {
LOG(PR_LOG_ERROR, ("Could not set track %d active", i));
}
}
if (mVideoTrack == -1) {
oggplay_set_callback_num_frames(mPlayer, mAudioTrack, OGGPLAY_FRAMES_PER_CALLBACK);
mCallbackPeriod = 1.0 / (float(mAudioRate) / OGGPLAY_FRAMES_PER_CALLBACK);
@ -1058,6 +1105,27 @@ void nsOggDecodeStateMachine::LoadOggHeaders()
oggplay_use_buffer(mPlayer, OGGPLAY_BUFFER_SIZE);
// 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.
{
nsAutoMonitor mon(mDecoder->GetMonitor());
if (mState != DECODER_STATE_SHUTDOWN &&
mContentLength >= 0 &&
mSeekable) {
// Don't hold the monitor during the duration
// call as it can issue seek requests
// and blocks until these are completed.
mon.Exit();
PRInt64 d = oggplay_get_duration(mPlayer);
mon.Enter();
mDuration = d;
}
if (mState == DECODER_STATE_SHUTDOWN)
return;
}
// Inform the element that we've loaded the Ogg metadata
nsCOMPtr<nsIRunnable> metadataLoadedEvent =
NS_NEW_RUNNABLE_METHOD(nsOggDecoder, mDecoder, MetadataLoaded);
@ -1097,10 +1165,13 @@ void nsOggDecoder::SetVolume(float volume)
float nsOggDecoder::GetDuration()
{
// Currently not implemented. Video Spec says to return
// NaN if unknown.
// TODO: return NaN
return 0.0;
nsAutoMonitor mon(mMonitor);
PRInt64 d = mDecodeStateMachine ? mDecodeStateMachine->GetDuration() : -1;
if (d >= 0) {
return static_cast<float>(d) / 1000.0;
}
return std::numeric_limits<float>::quiet_NaN();
}
nsOggDecoder::nsOggDecoder() :
@ -1109,8 +1180,9 @@ nsOggDecoder::nsOggDecoder() :
mCurrentTime(0.0),
mInitialVolume(0.0),
mRequestedSeekTime(-1.0),
mContentLength(0),
mContentLength(-1),
mNotifyOnShutdown(PR_FALSE),
mSeekable(PR_TRUE),
mReader(0),
mMonitor(0),
mPlayState(PLAY_STATE_PAUSED),
@ -1201,6 +1273,11 @@ nsresult nsOggDecoder::Load(nsIURI* aURI, nsIChannel* aChannel,
NS_ENSURE_SUCCESS(rv, rv);
mDecodeStateMachine = new nsOggDecodeStateMachine(this, mReader);
{
nsAutoMonitor mon(mMonitor);
mDecodeStateMachine->SetContentLength(mContentLength);
mDecodeStateMachine->SetSeekable(mSeekable);
}
ChangeState(PLAY_STATE_LOADING);
@ -1373,6 +1450,10 @@ PRInt64 nsOggDecoder::GetTotalBytes()
void nsOggDecoder::SetTotalBytes(PRInt64 aBytes)
{
mContentLength = aBytes;
if (mDecodeStateMachine) {
nsAutoMonitor mon(mMonitor);
mDecodeStateMachine->SetContentLength(aBytes);
}
}
void nsOggDecoder::UpdateBytesDownloaded(PRUint64 aBytes)
@ -1540,3 +1621,18 @@ void nsOggDecoder::PlaybackPositionChanged()
mElement->DispatchSimpleEvent(NS_LITERAL_STRING("timeupdate"));
}
}
void nsOggDecoder::SetSeekable(PRBool aSeekable)
{
mSeekable = aSeekable;
if (mDecodeStateMachine) {
nsAutoMonitor mon(mMonitor);
mDecodeStateMachine->SetSeekable(aSeekable);
}
}
PRBool nsOggDecoder::GetSeekable()
{
return mSeekable;
}

View File

@ -49,6 +49,7 @@ _TEST_FILES = test_autoplay.html \
test_controls.html \
test_currentTime.html \
test_defaultPlaybackRate.html \
test_duration1.html \
test_ended1.html \
test_ended2.html \
test_networkState.html \

View File

@ -0,0 +1,39 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Media test: seek test 1</title>
<script type="text/javascript" src="/MochiKit/MochiKit.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>
<video id='v'
src='seek.ogg'
onloadedmetadata='return startTest();'></video>
<pre id="test">
<script class="testbody" type="text/javascript">
// Test that getting the duration from a file works
var completed = false;
var timeout;
function startTest() {
if (completed)
return false;
var v = document.getElementById('v');
ok(Math.round(v.duration*1000) == 3833, "Check duration of video: " + v.duration);
completed = true;
clearTimeout(timeout);
SimpleTest.finish();
return false;
}
timeout = setTimeout(function () {
ok(false, "Test timed out");
SimpleTest.finish();
}, 60000);
SimpleTest.waitForExplicitFinish();
</script>
</pre>
</body>
</html>