зеркало из https://github.com/mozilla/gecko-dev.git
b=465458 Media selection algorithm changed in r2403 of HTML5 spec r=doublec sr=roc
--HG-- extra : rebase_source : 09a091ebb5c7a42480a423ac304aacedfa286c4c
This commit is contained in:
Родитель
36a2d9ec15
Коммит
f95265dfc7
|
@ -48,14 +48,30 @@ typedef PRUint16 nsMediaNetworkState;
|
|||
typedef PRUint16 nsMediaReadyState;
|
||||
|
||||
// Object representing a single execution of the media load algorithm.
|
||||
// Used by implicit load events so that they can be cancelled when Load()
|
||||
// is executed.
|
||||
// Note: When bug 465458 lands, all events are expected to do this, not
|
||||
// just implicit load events.
|
||||
// Used by asynchronous events so that they can be cancelled when Load()
|
||||
// is executed. Holds the list of candidate resources which the media
|
||||
// element attempts to load, and provides iteration over that list.
|
||||
class nsMediaLoad : public nsISupports
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
nsMediaLoad() : mPosition(0) {}
|
||||
~nsMediaLoad() {}
|
||||
|
||||
// Appends a candidate resource to the candidate list. List is populated
|
||||
// by nsHTMLMediaElement::GenerateCandidates().
|
||||
void AddCandidate(nsIURI *aURI);
|
||||
|
||||
// Returns the next candidate in the list, or null if at the end.
|
||||
already_AddRefed<nsIURI> GetNextCandidate();
|
||||
|
||||
PRBool HasMoreCandidates() { return mPosition < mCandidates.Count(); }
|
||||
private:
|
||||
// Candidate resource URIs.
|
||||
nsCOMArray<nsIURI> mCandidates;
|
||||
|
||||
// Index/iterator position in mCandidates.
|
||||
PRInt32 mPosition;
|
||||
};
|
||||
|
||||
class nsHTMLMediaElement : public nsGenericHTMLElement
|
||||
|
@ -213,22 +229,17 @@ public:
|
|||
virtual PRBool IsNodeOfType(PRUint32 aFlags) const;
|
||||
|
||||
/**
|
||||
* Queues an event to call Load().
|
||||
*/
|
||||
void QueueLoadTask();
|
||||
|
||||
/**
|
||||
* Returns the current nsMediaLoad object. Implicit load events store a
|
||||
* Returns the current nsMediaLoad object. Asynchronous events store a
|
||||
* reference to the nsMediaLoad object that was current when they were
|
||||
* enqueued, and if it has changed when they come to fire, they consider
|
||||
* themselves cancelled, and don't fire.
|
||||
* Note: When bug 465458 lands, all events are expected to do this, not
|
||||
* just implicit load events.
|
||||
*/
|
||||
nsMediaLoad* GetCurrentMediaLoad() { return mCurrentLoad; }
|
||||
|
||||
|
||||
protected:
|
||||
class nsMediaLoadListener;
|
||||
class MediaLoadListener;
|
||||
class LoadNextCandidateEvent;
|
||||
|
||||
/**
|
||||
* Figure out which resource to load (either the 'src' attribute or a
|
||||
|
@ -248,10 +259,12 @@ protected:
|
|||
nsIStreamListener **aListener);
|
||||
/**
|
||||
* Execute the initial steps of the load algorithm that ensure existing
|
||||
* loads are aborted and the element is emptied. Returns true if aborted,
|
||||
* false if emptied.
|
||||
* loads are aborted, the element is emptied, and a new load object is
|
||||
* created. Returns true if the current load aborts due to a new load being
|
||||
* started in event handlers triggered during this function call.
|
||||
*/
|
||||
PRBool AbortExistingLoads();
|
||||
|
||||
/**
|
||||
* Create a URI for the given aURISpec string.
|
||||
*/
|
||||
|
@ -263,6 +276,25 @@ protected:
|
|||
*/
|
||||
void NoSupportedMediaError();
|
||||
|
||||
// Performs "the candidate loop" step in the load algorithm. Attempts to
|
||||
// load candidates in the candidate media resource list until a channel opens
|
||||
// to a resource, then it returns. If the resource download fails, it will
|
||||
// set a callback to this function. Do not call this directly, call
|
||||
// QueueLoadNextCandidateTask() instead.
|
||||
void LoadNextCandidate();
|
||||
|
||||
// Populates the candidate resource list in mCurrentLoad.
|
||||
void GenerateCandidates();
|
||||
|
||||
// Enqueues an event to call Load() on the main thread. This will begin
|
||||
// the process of attempting to load candidate media resources from the
|
||||
// candidate resource list.
|
||||
void QueueLoadTask();
|
||||
|
||||
// Enqueues an event to resume trying to open resources in the candidate
|
||||
// loop.
|
||||
void QueueLoadNextCandidateTask();
|
||||
|
||||
nsRefPtr<nsMediaDecoder> mDecoder;
|
||||
|
||||
nsCOMPtr<nsIChannel> mChannel;
|
||||
|
@ -270,7 +302,8 @@ protected:
|
|||
// Error attribute
|
||||
nsCOMPtr<nsIDOMHTMLMediaError> mError;
|
||||
|
||||
// The current media load object.
|
||||
// The current media load object. Stores the list of candidate media
|
||||
// resources that we are attempting to load.
|
||||
nsRefPtr<nsMediaLoad> mCurrentLoad;
|
||||
|
||||
// Media loading flags. See:
|
||||
|
|
|
@ -83,54 +83,92 @@
|
|||
#include "nsWaveDecoder.h"
|
||||
#endif
|
||||
|
||||
class nsAsyncEventRunner : public nsRunnable
|
||||
|
||||
class nsMediaEvent : public nsRunnable
|
||||
{
|
||||
public:
|
||||
|
||||
nsMediaEvent(nsHTMLMediaElement* aElement) :
|
||||
mElement(aElement),
|
||||
mCurrentLoad(mElement->GetCurrentMediaLoad()) {}
|
||||
~nsMediaEvent() {}
|
||||
|
||||
NS_IMETHOD Run() = 0;
|
||||
|
||||
protected:
|
||||
PRBool IsCancelled() {
|
||||
return mElement->GetCurrentMediaLoad() != mCurrentLoad;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsHTMLMediaElement> mElement;
|
||||
nsRefPtr<nsMediaLoad> mCurrentLoad;
|
||||
};
|
||||
|
||||
|
||||
class nsAsyncEventRunner : public nsMediaEvent
|
||||
{
|
||||
private:
|
||||
nsString mName;
|
||||
nsCOMPtr<nsHTMLMediaElement> mElement;
|
||||
PRPackedBool mProgress;
|
||||
|
||||
public:
|
||||
nsAsyncEventRunner(const nsAString& aName, nsHTMLMediaElement* aElement, PRBool aProgress) :
|
||||
mName(aName), mElement(aElement), mProgress(aProgress)
|
||||
nsMediaEvent(aElement), mName(aName), mProgress(aProgress)
|
||||
{
|
||||
}
|
||||
|
||||
NS_IMETHOD Run() {
|
||||
// Silently cancel if our load has been cancelled.
|
||||
if (IsCancelled())
|
||||
return NS_OK;
|
||||
return mProgress ?
|
||||
mElement->DispatchProgressEvent(mName) :
|
||||
mElement->DispatchSimpleEvent(mName);
|
||||
}
|
||||
};
|
||||
|
||||
// Asynchronous runner which invokes Load() on the main thread.
|
||||
class nsMediaLoadEvent : public nsRunnable
|
||||
{
|
||||
class nsMediaLoadEvent : public nsMediaEvent {
|
||||
public:
|
||||
nsMediaLoadEvent(nsHTMLMediaElement *aMedia)
|
||||
: mMedia(aMedia), mCurrentLoad(mMedia->GetCurrentMediaLoad()) {}
|
||||
~nsMediaLoadEvent() {}
|
||||
|
||||
nsMediaLoadEvent(nsHTMLMediaElement *aElement)
|
||||
: nsMediaEvent(aElement) {}
|
||||
NS_IMETHOD Run() {
|
||||
// Only run the task if it's not been cancelled.
|
||||
if (mMedia && mMedia->GetCurrentMediaLoad() == mCurrentLoad)
|
||||
mMedia->Load();
|
||||
if (!IsCancelled())
|
||||
mElement->Load();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
nsCOMPtr<nsHTMLMediaElement> mMedia;
|
||||
nsRefPtr<nsMediaLoad> mCurrentLoad;
|
||||
};
|
||||
|
||||
class nsHTMLMediaElement::nsMediaLoadListener : public nsIStreamListener
|
||||
class nsHTMLMediaElement::LoadNextCandidateEvent : public nsMediaEvent {
|
||||
public:
|
||||
LoadNextCandidateEvent(nsHTMLMediaElement *aElement)
|
||||
: nsMediaEvent(aElement) {}
|
||||
NS_IMETHOD Run() {
|
||||
if (!IsCancelled())
|
||||
mElement->LoadNextCandidate();
|
||||
return NS_OK;
|
||||
}
|
||||
};
|
||||
|
||||
void nsHTMLMediaElement::QueueLoadTask()
|
||||
{
|
||||
nsCOMPtr<nsIRunnable> event = new nsMediaLoadEvent(this);
|
||||
NS_DispatchToMainThread(event);
|
||||
}
|
||||
|
||||
void nsHTMLMediaElement::QueueLoadNextCandidateTask()
|
||||
{
|
||||
nsCOMPtr<nsIRunnable> event = new LoadNextCandidateEvent(this);
|
||||
NS_DispatchToMainThread(event);
|
||||
}
|
||||
|
||||
class nsHTMLMediaElement::MediaLoadListener : public nsIStreamListener
|
||||
{
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIREQUESTOBSERVER
|
||||
NS_DECL_NSISTREAMLISTENER
|
||||
|
||||
public:
|
||||
nsMediaLoadListener(nsHTMLMediaElement* aElement)
|
||||
MediaLoadListener(nsHTMLMediaElement* aElement)
|
||||
: mElement(aElement)
|
||||
{
|
||||
NS_ABORT_IF_FALSE(mElement, "Must pass an element to call back");
|
||||
|
@ -141,9 +179,9 @@ private:
|
|||
nsCOMPtr<nsIStreamListener> mNextListener;
|
||||
};
|
||||
|
||||
NS_IMPL_ISUPPORTS2(nsHTMLMediaElement::nsMediaLoadListener, nsIRequestObserver, nsIStreamListener)
|
||||
NS_IMPL_ISUPPORTS2(nsHTMLMediaElement::MediaLoadListener, nsIRequestObserver, nsIStreamListener)
|
||||
|
||||
NS_IMETHODIMP nsHTMLMediaElement::nsMediaLoadListener::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
|
||||
NS_IMETHODIMP nsHTMLMediaElement::MediaLoadListener::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
|
||||
{
|
||||
nsresult rv = NS_OK;
|
||||
|
||||
|
@ -157,7 +195,9 @@ NS_IMETHODIMP nsHTMLMediaElement::nsMediaLoadListener::OnStartRequest(nsIRequest
|
|||
// If InitializeDecoderForChannel() returned an error, fire a network
|
||||
// error.
|
||||
if (NS_FAILED(rv) && !mNextListener && mElement) {
|
||||
mElement->NetworkError();
|
||||
// Load failed, attempt to load the next candidate resource. If there
|
||||
// are none, this will trigger a MEDIA_ERR_NONE_SUPPORTED error.
|
||||
mElement->QueueLoadNextCandidateTask();
|
||||
}
|
||||
// If InitializeDecoderForChannel did not return a listener (but may
|
||||
// have otherwise succeeded), we abort the connection since we aren't
|
||||
|
@ -172,7 +212,7 @@ NS_IMETHODIMP nsHTMLMediaElement::nsMediaLoadListener::OnStartRequest(nsIRequest
|
|||
return rv;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP nsHTMLMediaElement::nsMediaLoadListener::OnStopRequest(nsIRequest* aRequest, nsISupports* aContext,
|
||||
NS_IMETHODIMP nsHTMLMediaElement::MediaLoadListener::OnStopRequest(nsIRequest* aRequest, nsISupports* aContext,
|
||||
nsresult aStatus)
|
||||
{
|
||||
if (mNextListener) {
|
||||
|
@ -181,7 +221,7 @@ NS_IMETHODIMP nsHTMLMediaElement::nsMediaLoadListener::OnStopRequest(nsIRequest*
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP nsHTMLMediaElement::nsMediaLoadListener::OnDataAvailable(nsIRequest* aRequest, nsISupports* aContext,
|
||||
NS_IMETHODIMP nsHTMLMediaElement::MediaLoadListener::OnDataAvailable(nsIRequest* aRequest, nsISupports* aContext,
|
||||
nsIInputStream* aStream, PRUint32 aOffset,
|
||||
PRUint32 aCount)
|
||||
{
|
||||
|
@ -189,6 +229,22 @@ NS_IMETHODIMP nsHTMLMediaElement::nsMediaLoadListener::OnDataAvailable(nsIReques
|
|||
return mNextListener->OnDataAvailable(aRequest, aContext, aStream, aOffset, aCount);
|
||||
}
|
||||
|
||||
void
|
||||
nsMediaLoad::AddCandidate(nsIURI *aURI)
|
||||
{
|
||||
mCandidates.AppendObject(aURI);
|
||||
}
|
||||
|
||||
already_AddRefed<nsIURI>
|
||||
nsMediaLoad::GetNextCandidate()
|
||||
{
|
||||
if (mPosition == mCandidates.Count())
|
||||
return nsnull;
|
||||
nsCOMPtr<nsIURI> uri = mCandidates.ObjectAt(mPosition);
|
||||
mPosition++;
|
||||
return uri.forget();
|
||||
}
|
||||
|
||||
NS_IMPL_ISUPPORTS0(nsMediaLoad)
|
||||
|
||||
// nsIDOMHTMLMediaElement
|
||||
|
@ -240,6 +296,11 @@ NS_IMETHODIMP nsHTMLMediaElement::GetNetworkState(PRUint16 *aNetworkState)
|
|||
|
||||
PRBool nsHTMLMediaElement::AbortExistingLoads()
|
||||
{
|
||||
// Set a new load object. This will cause events which were enqueued
|
||||
// with a differnet load object to silently be cancelled.
|
||||
mCurrentLoad = new nsMediaLoad();
|
||||
nsRefPtr<nsMediaLoad> currentLoad = mCurrentLoad;
|
||||
|
||||
if (mDecoder) {
|
||||
mDecoder->Shutdown();
|
||||
mDecoder = nsnull;
|
||||
|
@ -249,7 +310,10 @@ PRBool nsHTMLMediaElement::AbortExistingLoads()
|
|||
mBegun = PR_FALSE;
|
||||
mError = new nsHTMLMediaError(nsIDOMHTMLMediaError::MEDIA_ERR_ABORTED);
|
||||
DispatchProgressEvent(NS_LITERAL_STRING("abort"));
|
||||
return PR_TRUE;
|
||||
if (mCurrentLoad != currentLoad) {
|
||||
// A new load was triggered in handler, bail out of this load.
|
||||
return PR_TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
mError = nsnull;
|
||||
|
@ -265,13 +329,18 @@ PRBool nsHTMLMediaElement::AbortExistingLoads()
|
|||
// TODO: The current playback position must be set to 0.
|
||||
// TODO: The currentLoop DOM attribute must be set to 0.
|
||||
DispatchSimpleEvent(NS_LITERAL_STRING("emptied"));
|
||||
if (mCurrentLoad != currentLoad) {
|
||||
// A new load was triggered in handler, bail out of this load.
|
||||
return PR_TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
return PR_FALSE;
|
||||
}
|
||||
|
||||
void nsHTMLMediaElement::NoSupportedMediaError()
|
||||
{
|
||||
NS_ASSERTION(mCurrentLoad && !mCurrentLoad->HasMoreCandidates(),
|
||||
"Should have exhausted all candidates");
|
||||
mError = new nsHTMLMediaError(nsIDOMHTMLMediaError::MEDIA_ERR_NONE_SUPPORTED);
|
||||
mBegun = PR_FALSE;
|
||||
DispatchAsyncProgressEvent(NS_LITERAL_STRING("error"));
|
||||
|
@ -279,21 +348,9 @@ void nsHTMLMediaElement::NoSupportedMediaError()
|
|||
DispatchAsyncSimpleEvent(NS_LITERAL_STRING("emptied"));
|
||||
}
|
||||
|
||||
void nsHTMLMediaElement::QueueLoadTask()
|
||||
{
|
||||
nsCOMPtr<nsIRunnable> event = new nsMediaLoadEvent(this);
|
||||
NS_DispatchToMainThread(event);
|
||||
}
|
||||
|
||||
/* void load (); */
|
||||
NS_IMETHODIMP nsHTMLMediaElement::Load()
|
||||
{
|
||||
// Set a new load object. This will cause implicit load events which were
|
||||
// enqueued before with a different load object to silently be cancelled.
|
||||
// Note: When bug 465458 lands, all events are expected to do this, not
|
||||
// just implicit load events.
|
||||
mCurrentLoad = new nsMediaLoad();
|
||||
|
||||
if (AbortExistingLoads())
|
||||
return NS_OK;
|
||||
|
||||
|
@ -301,99 +358,101 @@ NS_IMETHODIMP nsHTMLMediaElement::Load()
|
|||
mBegun = PR_TRUE;
|
||||
DispatchAsyncProgressEvent(NS_LITERAL_STRING("loadstart"));
|
||||
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
nsresult rv = PickMediaElement(getter_AddRefs(uri));
|
||||
if (NS_FAILED(rv)) {
|
||||
NoSupportedMediaError();
|
||||
return NS_OK;
|
||||
}
|
||||
GenerateCandidates();
|
||||
|
||||
if (mChannel) {
|
||||
mChannel->Cancel(NS_BINDING_ABORTED);
|
||||
mChannel = nsnull;
|
||||
}
|
||||
|
||||
PRInt16 shouldLoad = nsIContentPolicy::ACCEPT;
|
||||
rv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_MEDIA,
|
||||
uri,
|
||||
NodePrincipal(),
|
||||
this,
|
||||
EmptyCString(), // mime type
|
||||
nsnull, // extra
|
||||
&shouldLoad,
|
||||
nsContentUtils::GetContentPolicy(),
|
||||
nsContentUtils::GetSecurityManager());
|
||||
if (NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad)) {
|
||||
NoSupportedMediaError();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
rv = NS_NewChannel(getter_AddRefs(mChannel),
|
||||
uri,
|
||||
nsnull,
|
||||
nsnull,
|
||||
nsnull,
|
||||
nsIRequest::LOAD_NORMAL);
|
||||
if (NS_FAILED(rv)) {
|
||||
NetworkError();
|
||||
return NS_OK;
|
||||
}
|
||||
// The listener holds a strong reference to us. This creates a reference
|
||||
// cycle which is manually broken in the listener's OnStartRequest method
|
||||
// after it is finished with the element.
|
||||
nsCOMPtr<nsIStreamListener> loadListener = new nsMediaLoadListener(this);
|
||||
NS_ENSURE_TRUE(loadListener, NS_ERROR_OUT_OF_MEMORY);
|
||||
|
||||
nsCOMPtr<nsIStreamListener> listener;
|
||||
if (ShouldCheckAllowOrigin()) {
|
||||
listener = new nsCrossSiteListenerProxy(loadListener,
|
||||
NodePrincipal(),
|
||||
mChannel,
|
||||
PR_FALSE,
|
||||
&rv);
|
||||
NS_ENSURE_TRUE(listener, NS_ERROR_OUT_OF_MEMORY);
|
||||
if (NS_FAILED(rv)) {
|
||||
NoSupportedMediaError();
|
||||
return NS_OK;
|
||||
}
|
||||
} else {
|
||||
rv = nsContentUtils::GetSecurityManager()->
|
||||
CheckLoadURIWithPrincipal(NodePrincipal(),
|
||||
uri,
|
||||
nsIScriptSecurityManager::STANDARD);
|
||||
if (NS_FAILED(rv)) {
|
||||
NoSupportedMediaError();
|
||||
return NS_OK;
|
||||
}
|
||||
listener = loadListener;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(mChannel);
|
||||
if (hc) {
|
||||
// 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.
|
||||
hc->SetRequestHeader(NS_LITERAL_CSTRING("Range"),
|
||||
NS_LITERAL_CSTRING("bytes=0-"),
|
||||
PR_FALSE);
|
||||
}
|
||||
|
||||
rv = mChannel->AsyncOpen(listener, nsnull);
|
||||
if (NS_FAILED(rv)) {
|
||||
// OnStartRequest is guaranteed to be called if the open succeeds. If
|
||||
// the open failed, the listener's OnStartRequest will never be called,
|
||||
// so we need to break the element->channel->listener->element reference
|
||||
// cycle here. The channel holds the only reference to the listener,
|
||||
// and is useless now anyway, so drop our reference to it to allow it to
|
||||
// be destroyed.
|
||||
mChannel = nsnull;
|
||||
NetworkError();
|
||||
return NS_OK;
|
||||
}
|
||||
QueueLoadNextCandidateTask();
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void nsHTMLMediaElement::LoadNextCandidate()
|
||||
{
|
||||
NS_ASSERTION(mCurrentLoad, "Need a load object");
|
||||
|
||||
while (mCurrentLoad->HasMoreCandidates()) {
|
||||
nsresult rv;
|
||||
nsCOMPtr<nsIURI> uri = mCurrentLoad->GetNextCandidate();
|
||||
|
||||
if (mChannel) {
|
||||
mChannel->Cancel(NS_BINDING_ABORTED);
|
||||
mChannel = nsnull;
|
||||
}
|
||||
|
||||
PRInt16 shouldLoad = nsIContentPolicy::ACCEPT;
|
||||
rv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_MEDIA,
|
||||
uri,
|
||||
NodePrincipal(),
|
||||
this,
|
||||
EmptyCString(), // mime type
|
||||
nsnull, // extra
|
||||
&shouldLoad,
|
||||
nsContentUtils::GetContentPolicy(),
|
||||
nsContentUtils::GetSecurityManager());
|
||||
if (NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad)) continue;
|
||||
|
||||
rv = NS_NewChannel(getter_AddRefs(mChannel),
|
||||
uri,
|
||||
nsnull,
|
||||
nsnull,
|
||||
nsnull,
|
||||
nsIRequest::LOAD_NORMAL);
|
||||
if (NS_FAILED(rv)) continue;
|
||||
|
||||
// The listener holds a strong reference to us. This creates a reference
|
||||
// cycle which is manually broken in the listener's OnStartRequest method
|
||||
// after it is finished with the element.
|
||||
nsCOMPtr<nsIStreamListener> loadListener = new MediaLoadListener(this);
|
||||
if (!loadListener) continue;
|
||||
|
||||
nsCOMPtr<nsIStreamListener> listener;
|
||||
if (ShouldCheckAllowOrigin()) {
|
||||
listener = new nsCrossSiteListenerProxy(loadListener,
|
||||
NodePrincipal(),
|
||||
mChannel,
|
||||
PR_FALSE,
|
||||
&rv);
|
||||
if (!listener || NS_FAILED(rv)) continue;
|
||||
} else {
|
||||
rv = nsContentUtils::GetSecurityManager()->
|
||||
CheckLoadURIWithPrincipal(NodePrincipal(),
|
||||
uri,
|
||||
nsIScriptSecurityManager::STANDARD);
|
||||
if (NS_FAILED(rv)) continue;
|
||||
listener = loadListener;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(mChannel);
|
||||
if (hc) {
|
||||
// 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.
|
||||
hc->SetRequestHeader(NS_LITERAL_CSTRING("Range"),
|
||||
NS_LITERAL_CSTRING("bytes=0-"),
|
||||
PR_FALSE);
|
||||
}
|
||||
|
||||
rv = mChannel->AsyncOpen(listener, nsnull);
|
||||
if (NS_FAILED(rv)) {
|
||||
// OnStartRequest is guaranteed to be called if the open succeeds. If
|
||||
// the open failed, the listener's OnStartRequest will never be called,
|
||||
// so we need to break the element->channel->listener->element reference
|
||||
// cycle here. The channel holds the only reference to the listener,
|
||||
// and is useless now anyway, so drop our reference to it to allow it to
|
||||
// be destroyed.
|
||||
mChannel = nsnull;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Else the channel must be open and starting to download. If it encounters
|
||||
// a non-catestrophic failure, it will set a new task to continue loading
|
||||
// the candidates.
|
||||
return;
|
||||
}
|
||||
|
||||
// Must have exhausted all candidates.
|
||||
NoSupportedMediaError();
|
||||
}
|
||||
|
||||
nsresult nsHTMLMediaElement::LoadWithChannel(nsIChannel *aChannel,
|
||||
nsIStreamListener **aListener)
|
||||
{
|
||||
|
@ -981,6 +1040,57 @@ nsresult nsHTMLMediaElement::NewURIFromString(const nsAutoString& aURISpec, nsIU
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
// Implements 'generate the list of potential media resources' as per:
|
||||
// http://www.whatwg.org/specs/web-apps/current-work/multipage/video.html#generate-the-list-of-potential-media-resources
|
||||
void nsHTMLMediaElement::GenerateCandidates()
|
||||
{
|
||||
NS_ASSERTION(mCurrentLoad, "Need a nsMediaLoad Object");
|
||||
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
nsresult res;
|
||||
nsAutoString src;
|
||||
|
||||
// If we have a src attribute on the media element, just use that only.
|
||||
if (GetAttr(kNameSpaceID_None, nsGkAtoms::src, src)) {
|
||||
res = NewURIFromString(src, getter_AddRefs(uri));
|
||||
if (NS_SUCCEEDED(res)) {
|
||||
mCurrentLoad->AddCandidate(uri);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
PRUint32 count = GetChildCount();
|
||||
for (PRUint32 i = 0; i < count; ++i) {
|
||||
nsIContent* child = GetChildAt(i);
|
||||
NS_ASSERTION(child, "GetChildCount lied!");
|
||||
|
||||
nsCOMPtr<nsIContent> source = do_QueryInterface(child);
|
||||
|
||||
// Only check source element children.
|
||||
if (!source ||
|
||||
source->Tag() != nsGkAtoms::source ||
|
||||
!source->IsNodeOfType(nsINode::eHTML))
|
||||
continue;
|
||||
|
||||
nsAutoString type;
|
||||
|
||||
// Must have src attribute.
|
||||
if (!source->GetAttr(kNameSpaceID_None, nsGkAtoms::src, src))
|
||||
continue;
|
||||
|
||||
// If we have a type attribyte, it must be valid.
|
||||
if (source->GetAttr(kNameSpaceID_None, nsGkAtoms::type, type) &&
|
||||
GetCanPlay(type) == CANPLAY_NO)
|
||||
continue;
|
||||
|
||||
res = NewURIFromString(src, getter_AddRefs(uri));
|
||||
if (NS_SUCCEEDED(res)) {
|
||||
mCurrentLoad->AddCandidate(uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
nsresult nsHTMLMediaElement::PickMediaElement(nsIURI** aURI)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aURI);
|
||||
|
|
|
@ -328,7 +328,7 @@ protected:
|
|||
void HandleAudioData(FrameData* aFrame, OggPlayAudioData* aAudioData, int aSize);
|
||||
|
||||
// These methods can only be called on the decoding thread.
|
||||
void LoadOggHeaders();
|
||||
void LoadOggHeaders(nsChannelReader* aReader);
|
||||
|
||||
// Initializes and opens the audio stream. Called from the decode
|
||||
// thread only. Must be called with the decode monitor held.
|
||||
|
@ -909,7 +909,7 @@ nsresult nsOggDecodeStateMachine::Run()
|
|||
|
||||
case DECODER_STATE_DECODING_METADATA:
|
||||
mon.Exit();
|
||||
LoadOggHeaders();
|
||||
LoadOggHeaders(reader);
|
||||
mon.Enter();
|
||||
|
||||
if (mState == DECODER_STATE_DECODING_METADATA) {
|
||||
|
@ -1170,10 +1170,10 @@ nsresult nsOggDecodeStateMachine::Run()
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
void nsOggDecodeStateMachine::LoadOggHeaders()
|
||||
void nsOggDecodeStateMachine::LoadOggHeaders(nsChannelReader* aReader)
|
||||
{
|
||||
LOG(PR_LOG_DEBUG, ("Loading Ogg Headers"));
|
||||
mPlayer = oggplay_open_with_reader(mDecoder->GetReader());
|
||||
mPlayer = oggplay_open_with_reader(aReader);
|
||||
if (mPlayer) {
|
||||
LOG(PR_LOG_DEBUG, ("There are %d tracks", oggplay_get_num_tracks(mPlayer)));
|
||||
|
||||
|
|
|
@ -62,6 +62,8 @@ ifdef MOZ_OGG
|
|||
_TEST_FILES += \
|
||||
test_access_control.html \
|
||||
file_access_controls.html \
|
||||
test_async_abort_events.html \
|
||||
test_async_load_source_append.html \
|
||||
test_bug448534.html \
|
||||
test_bug461281.html \
|
||||
test_bug468190.html \
|
||||
|
@ -77,6 +79,7 @@ _TEST_FILES += \
|
|||
test_ended2.html \
|
||||
test_error_on_404.html \
|
||||
test_onloadedmetadata.html \
|
||||
test_load_candidates.html \
|
||||
test_load_coalescing.html \
|
||||
test_play.html \
|
||||
test_progress1.html \
|
||||
|
|
|
@ -117,7 +117,6 @@ function nextTest() {
|
|||
//dump("gTestNum == gTests.length\n");
|
||||
if (!gTestedRemoved) {
|
||||
// Repeat all tests with element removed from doc, should get same result.
|
||||
gVideo.parentNode.removeChild(gVideo);
|
||||
gTestedRemoved = true;
|
||||
gTestNum = 0;
|
||||
} else {
|
||||
|
@ -130,6 +129,9 @@ function nextTest() {
|
|||
gExpectedResult = gTests[gTestNum].result;
|
||||
gTestDescription = gTests[gTestNum].description;
|
||||
//dump("Starting test " + gTestNum + " at " + gTests[gTestNum].url + " expecting:" + gExpectedResult + "\n");
|
||||
|
||||
if (gVideo && gVideo.parentNode)
|
||||
gVideo.parentNode.removeChild(gVideo);
|
||||
|
||||
gVideo = createVideo();
|
||||
gVideo.src = gTests[gTestNum].url;
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=478299
|
||||
-->
|
||||
<head>
|
||||
<title>Test for Bug 478299</title>
|
||||
<script type="application/javascript" src="/MochiKit/MochiKit.js"></script>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=478299">Mozilla Bug 478299</a>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none">
|
||||
|
||||
</div>
|
||||
<pre id="test">
|
||||
<script type="application/javascript">
|
||||
|
||||
/** Test for Bug 478299 **/
|
||||
|
||||
// This tests that we bail out of a load if we do another load in an abort handler.
|
||||
// See: https://bugzilla.mozilla.org/show_bug.cgi?id=465458#c4
|
||||
|
||||
|
||||
var v = null;
|
||||
var gAbortCount = 0;
|
||||
var gErrorCount = 0;
|
||||
var gEmptiedCount = 0;
|
||||
var gLoadedDataCount = 0;
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
function errorListener() {
|
||||
gErrorCount++;
|
||||
}
|
||||
|
||||
function emptiedListener() {
|
||||
gEmptiedCount++;
|
||||
}
|
||||
|
||||
function loadedDataListener() {
|
||||
gLoadedDataCount++;
|
||||
is(gLoadedDataCount, 1, "Should only get 1 load");
|
||||
is(gAbortCount, 1, "Should only get 1 abort");
|
||||
is(gEmptiedCount, 1, "Should only get 1 emptied");
|
||||
is(gErrorCount, 0, "Shouldn't get any errors");
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
function abortListener() {
|
||||
gAbortCount++;
|
||||
if (gAbortCount == 1) {
|
||||
v.src = '320x240.ogv';
|
||||
v.load();
|
||||
}
|
||||
}
|
||||
|
||||
v = document.createElement('video');
|
||||
v.addEventListener('error', errorListener, false);
|
||||
v.addEventListener('emptied', emptiedListener, false);
|
||||
v.addEventListener('loadeddata', loadedDataListener, false);
|
||||
v.addEventListener('abort', abortListener, false);
|
||||
|
||||
is(v.networkState, HTMLMediaElement.NETWORK_EMPTY, "NW State invalid.");
|
||||
v.load();
|
||||
ok(v.networkState > HTMLMediaElement.NETWORK_EMPTY, "NW State invalid.");
|
||||
|
||||
// Cause another load. This will abort the previous load, causing 'abort' event to fire.
|
||||
// That will trigger the abort handler.
|
||||
// Abort handler adds 'src' attribute, and starts a new load. This aborts the previous load,
|
||||
// so it won't fire emptied for the first abort. The second abort then causes 'abort' and
|
||||
// 'emptied' events. The load should then succeed, causing 'loadeddata' event.
|
||||
v.load();
|
||||
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,89 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=478299
|
||||
-->
|
||||
<head>
|
||||
<title>Test for Bug 478299</title>
|
||||
<script type="application/javascript" src="/MochiKit/MochiKit.js"></script>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=478299">Mozilla Bug 478299</a>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none">
|
||||
|
||||
</div>
|
||||
<pre id="test">
|
||||
<script type="application/javascript">
|
||||
|
||||
/** Test for Bug 478299 **/
|
||||
|
||||
// This tests that we only uses sources added before a load is triggered in an async load.
|
||||
// See: https://bugzilla.mozilla.org/show_bug.cgi?id=465458#c4
|
||||
|
||||
var gLoadStartsCount = 0;
|
||||
var gLoadedCount = 0;
|
||||
var gAbortCount = 0;
|
||||
var gEmptiedCount = 0;
|
||||
var gErrorCount = 0;
|
||||
|
||||
function errorHandler(e) {
|
||||
gErrorCount++;
|
||||
is(gLoadStartsCount, 1, "Should have received 1 loadstart.");
|
||||
is(gLoadedCount, 0, "Should not have loaded");
|
||||
is(gAbortCount, 0, "Shouldn't have aborted any loads");
|
||||
is(gErrorCount, 1, "Shouldn't have received errors");
|
||||
is(gEmptiedCount, 0, "Shouldn't have reEmptiedCount should be 1");
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
function abortHandler() {
|
||||
gAbortCount++;
|
||||
}
|
||||
|
||||
function startHandler() {
|
||||
gLoadStartsCount++;
|
||||
}
|
||||
|
||||
function loadedHandler() {
|
||||
gLoadedCount++;
|
||||
}
|
||||
|
||||
function emptiedHandler() {
|
||||
gEmptiedCount++;
|
||||
}
|
||||
|
||||
function volumeChangeHandler() {
|
||||
gVolumeChanged++;
|
||||
}
|
||||
|
||||
var v = document.createElement('video');
|
||||
v.addEventListener('error', errorHandler, false);
|
||||
v.addEventListener('abort', abortHandler, false);
|
||||
v.addEventListener('loadeddata', loadedHandler, false);
|
||||
v.addEventListener('loadstart', startHandler, false);
|
||||
v.addEventListener('emptied', emptiedHandler, false);
|
||||
|
||||
// Create a source to a 404. Expect error on load.
|
||||
var s1 = document.createElement('source');
|
||||
s1.type = "application/ogg";
|
||||
s1.src = "404.ogv";
|
||||
v.appendChild(s1);
|
||||
v.load();
|
||||
|
||||
// Create a source to a valid resource.
|
||||
var s2 = document.createElement('source');
|
||||
s2.type = "application/ogg";
|
||||
s2.src = "320x240.ogv";
|
||||
|
||||
// We expect that this source will not be considered during load.
|
||||
v.appendChild(s2);
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
|
@ -13,19 +13,49 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=463162
|
|||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=463162">Mozilla Bug 463162</a>
|
||||
<audio id='a1' onerror="event.stopPropagation();"><sauce type="audio/x-wav" src="r11025_s16_c1.wav"/></audio>
|
||||
<audio id='a2' onerror="event.stopPropagation();"><source type="audio/x-wav" src="r11025_s16_c1.wav"/></audio>
|
||||
<audio id='a3' onerror="event.stopPropagation();"><html:source type="audio/x-wav" src="r11025_s16_c1.wav"/></audio>
|
||||
<audio id='a4' onerror="event.stopPropagation();"><svg:source type="audio/x-wav" src="r11025_s16_c1.wav"/></audio>
|
||||
<pre id="test">
|
||||
|
||||
<script class="testbody" type="text/javascript">
|
||||
<![CDATA[
|
||||
ok($("a1").networkState == HTMLMediaElement.NETWORK_EMPTY, "audio a1 with bogus child should not be loading");
|
||||
ok($("a2").networkState >= HTMLMediaElement.NETWORK_LOADING, "audio a2 with valid child should be loading");
|
||||
ok($("a3").networkState >= HTMLMediaElement.NETWORK_LOADING, "audio a3 with valid child should be loading");
|
||||
ok($("a4").networkState == HTMLMediaElement.NETWORK_EMPTY, "audio a4 with bogus child should not be loading");
|
||||
|
||||
var gExpectedResult = {
|
||||
'a1' : 'error',
|
||||
'a2' : 'loaded',
|
||||
'a3' : 'loaded',
|
||||
'a4' : 'error',
|
||||
};
|
||||
|
||||
var gResultCount = 0;
|
||||
|
||||
function onError(event, id) {
|
||||
event.stopPropagation();
|
||||
is('error', gExpectedResult[id], 'unexpected error loading ' + id);
|
||||
gResultCount++;
|
||||
dump('error('+id+') expected ' + gExpectedResult[id] + ' gResultCount=' + gResultCount + '\n');
|
||||
if (gResultCount == 4)
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
function onMetaData(id) {
|
||||
is('loaded', gExpectedResult[id], 'unexpected loadedmetadata loading ' + id);
|
||||
gResultCount++;
|
||||
dump('onMetaData('+id+') expected ' + gExpectedResult[id] + ' gResultCount=' + gResultCount + '\n');
|
||||
if (gResultCount == 4)
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
]]>
|
||||
</script>
|
||||
|
||||
|
||||
<audio id='a1' onerror="onError(event, 'a1');" onloadedmetadata="onMetaData('a1');"><sauce type="audio/x-wav" src="r11025_s16_c1.wav"/></audio>
|
||||
<audio id='a2' onerror="onError(event, 'a2');" onloadedmetadata="onMetaData('a2');"><source type="audio/x-wav" src="r11025_s16_c1.wav"/></audio>
|
||||
<audio id='a3' onerror="onError(event, 'a3');" onloadedmetadata="onMetaData('a3');"><html:source type="audio/x-wav" src="r11025_s16_c1.wav"/></audio>
|
||||
<audio id='a4' onerror="onError(event, 'a4');" onloadedmetadata="onMetaData('a4');"><svg:source type="audio/x-wav" src="r11025_s16_c1.wav"/></audio>
|
||||
|
||||
<pre id="test">
|
||||
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=465458
|
||||
-->
|
||||
<head>
|
||||
<title>Test for Bug 465458</title>
|
||||
<script type="application/javascript" src="/MochiKit/MochiKit.js"></script>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=465458">Mozilla Bug 465458</a>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none">
|
||||
|
||||
</div>
|
||||
<pre id="test">
|
||||
<script type="application/javascript">
|
||||
|
||||
/** Test for Bug 465458 **/
|
||||
|
||||
var gError = false;
|
||||
|
||||
function finish() {
|
||||
is(gError, false, "Shouldn't have thrown an error");
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
function errorHandler(e) {
|
||||
e.stopPropagation();
|
||||
gError = true;
|
||||
finish();
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
|
||||
<video onerror="errorHandler(event)" id="v" onloadeddata="finish();">
|
||||
<source src="404.ogv" type="application/ogg"></source>
|
||||
<source src="320x240.ogv" type="application/ogg"></source>
|
||||
</video>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -14,9 +14,6 @@ var v1 = document.getElementById('v1');
|
|||
var a1 = document.getElementById('a1');
|
||||
var passed = true;
|
||||
|
||||
is(v1.networkState, 0);
|
||||
is(a1.networkState, 0);
|
||||
|
||||
try {
|
||||
v1.networkState = 0;
|
||||
a1.networkState = 0;
|
||||
|
|
|
@ -49,7 +49,9 @@ v1.addEventListener('loadeddata', function() { loaded(v1); }, false);
|
|||
a1.addEventListener('loadeddata', function() { loaded(a1); }, false);
|
||||
|
||||
v1.appendChild(newSource());
|
||||
v1.load();
|
||||
a1.appendChild(newSource());
|
||||
a1.load();
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
</script>
|
||||
|
|
Загрузка…
Ссылка в новой задаче