From d483c98f06f32e37c195b0e020cef341385ace5c Mon Sep 17 00:00:00 2001 From: Matthew Gregan Date: Wed, 3 Dec 2008 10:16:15 +1300 Subject: [PATCH] Bug 464158. Implement media type switching based on the MIME type of the loaded resouce. r=doublec,sr=roc --- .../html/content/public/nsHTMLMediaElement.h | 17 +- .../html/content/src/nsHTMLMediaElement.cpp | 275 +++++++++++++----- content/media/video/test/Makefile.in | 2 + content/media/video/test/test_bug463162.xhtml | 31 ++ .../video/test/test_media_selection.html | 143 +++++++++ 5 files changed, 390 insertions(+), 78 deletions(-) create mode 100644 content/media/video/test/test_bug463162.xhtml create mode 100644 content/media/video/test/test_media_selection.html diff --git a/content/html/content/public/nsHTMLMediaElement.h b/content/html/content/public/nsHTMLMediaElement.h index 578a2201e553..a7f39e605190 100644 --- a/content/html/content/public/nsHTMLMediaElement.h +++ b/content/html/content/public/nsHTMLMediaElement.h @@ -173,19 +173,15 @@ public: protected: /** - * Figure out which resource to load (either the 'src' attribute or - * a child) and create the decoder for it. + * Figure out which resource to load (either the 'src' attribute or a + * child) and return the associated URI. */ - nsresult PickMediaElement(); + nsresult PickMediaElement(nsIURI** aURI); /** * Create a decoder for the given aMIMEType. Returns false if we * were unable to create the decoder. */ PRBool CreateDecoder(const nsACString& aMIMEType); - /** - * Initialize a decoder to load the given URI. - */ - nsresult InitializeDecoder(const nsAString& aURISpec); /** * Initialize a decoder to load the given channel. The decoder's stream * listener is returned via aListener. @@ -193,8 +189,15 @@ protected: nsresult InitializeDecoderForChannel(nsIChannel *aChannel, nsIStreamListener **aListener); + /** + * Create a URI for the given aURISpec string. + */ + nsresult NewURIFromString(const nsAutoString& aURISpec, nsIURI** aURI); + nsRefPtr mDecoder; + nsCOMPtr mChannel; + // Error attribute nsCOMPtr mError; diff --git a/content/html/content/src/nsHTMLMediaElement.cpp b/content/html/content/src/nsHTMLMediaElement.cpp index 6c5a52889149..f91756ba1ae9 100644 --- a/content/html/content/src/nsHTMLMediaElement.cpp +++ b/content/html/content/src/nsHTMLMediaElement.cpp @@ -96,6 +96,64 @@ public: } }; +class nsMediaLoadListener : public nsIStreamListener +{ + NS_DECL_ISUPPORTS + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + +public: + nsMediaLoadListener(nsHTMLMediaElement* aElement) + : mElement(aElement) + { + NS_ABORT_IF_FALSE(mElement, "Must pass an element to call back"); + } + +private: + nsRefPtr mElement; + nsCOMPtr mNextListener; +}; + +NS_IMPL_ISUPPORTS2(nsMediaLoadListener, nsIRequestObserver, nsIStreamListener) + +NS_IMETHODIMP nsMediaLoadListener::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext) +{ + nsresult rv; + + nsCOMPtr channel = do_QueryInterface(aRequest); + if (channel && + mElement && + NS_SUCCEEDED(mElement->LoadWithChannel(channel, getter_AddRefs(mNextListener))) && + mNextListener) { + rv = mNextListener->OnStartRequest(aRequest, aContext); + } else { + // If LoadWithChannel did not return a listener, we abort the connection + // since we aren't interested in keeping the channel alive ourselves. + rv = NS_BINDING_ABORTED; + } + + // The element is only needed until we've had a chance to call + // LoadWithChannel. + mElement = nsnull; + + return rv; +} + +NS_IMETHODIMP nsMediaLoadListener::OnStopRequest(nsIRequest* aRequest, nsISupports* aContext, nsresult aStatus) +{ + if (mNextListener) { + return mNextListener->OnStopRequest(aRequest, aContext, aStatus); + } + return NS_OK; +} + +NS_IMETHODIMP nsMediaLoadListener::OnDataAvailable(nsIRequest* aRequest, nsISupports* aContext, + nsIInputStream* aStream, PRUint32 aOffset, PRUint32 aCount) +{ + NS_ABORT_IF_FALSE(mNextListener, "Must have a listener"); + return mNextListener->OnDataAvailable(aRequest, aContext, aStream, aOffset, aCount); +} + // nsIDOMHTMLMediaElement NS_IMPL_URI_ATTR(nsHTMLMediaElement, Src, src) NS_IMPL_BOOL_ATTR(nsHTMLMediaElement, Controls, controls) @@ -153,17 +211,69 @@ NS_IMETHODIMP nsHTMLMediaElement::GetTotalBytes(PRUint32 *aTotalBytes) /* void load (); */ NS_IMETHODIMP nsHTMLMediaElement::Load() { - return LoadWithChannel(nsnull, nsnull); + nsCOMPtr uri; + nsresult rv = PickMediaElement(getter_AddRefs(uri)); + if (NS_FAILED(rv)) + return rv; + + if (mChannel) { + mChannel->Cancel(NS_BINDING_ABORTED); + mChannel = nsnull; + } + + rv = NS_NewChannel(getter_AddRefs(mChannel), + uri, + nsnull, + nsnull, + nsnull, + nsIRequest::LOAD_NORMAL); + NS_ENSURE_SUCCESS(rv, rv); + + // 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 listener = new nsMediaLoadListener(this); + NS_ENSURE_TRUE(listener, NS_ERROR_OUT_OF_MEMORY); + + nsCOMPtr 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; + return rv; + } + + mNetworkState = nsIDOMHTMLMediaElement::LOADING; + + return NS_OK; } nsresult nsHTMLMediaElement::LoadWithChannel(nsIChannel *aChannel, nsIStreamListener **aListener) { - NS_ASSERTION((aChannel == nsnull) == (aListener == nsnull), - "channel and listener should both be null or both non-null"); + NS_ENSURE_ARG_POINTER(aChannel); + NS_ENSURE_ARG_POINTER(aListener); - if (aListener) { - *aListener = nsnull; + *aListener = nsnull; + + if (mDecoder) { + mDecoder->ElementUnavailable(); + mDecoder->Shutdown(); + mDecoder = nsnull; } if (mBegun) { @@ -189,13 +299,10 @@ nsresult nsHTMLMediaElement::LoadWithChannel(nsIChannel *aChannel, DispatchSimpleEvent(NS_LITERAL_STRING("emptied")); } - nsresult rv; - if (aChannel) { - rv = InitializeDecoderForChannel(aChannel, aListener); - } else { - rv = PickMediaElement(); + nsresult rv = InitializeDecoderForChannel(aChannel, aListener); + if (NS_FAILED(rv)) { + return rv; } - NS_ENSURE_SUCCESS(rv, rv); mBegun = PR_TRUE; mEnded = PR_FALSE; @@ -366,6 +473,10 @@ nsHTMLMediaElement::~nsHTMLMediaElement() mDecoder->Shutdown(); mDecoder = nsnull; } + if (mChannel) { + mChannel->Cancel(NS_BINDING_ABORTED); + mChannel = nsnull; + } } NS_IMETHODIMP @@ -564,6 +675,21 @@ void nsHTMLMediaElement::ShutdownMediaTypes() } } +static PRBool CanDecode(const nsACString& aType) +{ +#ifdef MOZ_OGG + if (IsOggType(aType)) { + return PR_TRUE; + } +#endif +#ifdef MOZ_WAVE + if (IsWaveType(aType)) { + return PR_TRUE; + } +#endif + return PR_FALSE; +} + PRBool nsHTMLMediaElement::CreateDecoder(const nsACString& aType) { #ifdef MOZ_OGG @@ -596,81 +722,84 @@ nsresult nsHTMLMediaElement::InitializeDecoderForChannel(nsIChannel *aChannel, mNetworkState = nsIDOMHTMLMediaElement::LOADING; mDecoder->ElementAvailable(this); - + return mDecoder->Load(nsnull, aChannel, aListener); } -nsresult nsHTMLMediaElement::PickMediaElement() +nsresult nsHTMLMediaElement::NewURIFromString(const nsAutoString& aURISpec, nsIURI** aURI) { - // Implements: - // http://www.whatwg.org/specs/web-apps/current-work/#pick-a - nsAutoString src; - if (GetAttr(kNameSpaceID_None, nsGkAtoms::src, src)) { -#ifdef MOZ_OGG - // Currently assuming an Ogg file - // TODO: Instantiate decoder based on type - if (mDecoder) { - mDecoder->ElementUnavailable(); - mDecoder->Shutdown(); - mDecoder = nsnull; - } + NS_ENSURE_ARG_POINTER(aURI); - mDecoder = new nsOggDecoder(); - if (mDecoder && !mDecoder->Init()) { - mDecoder = nsnull; - } -#endif - return InitializeDecoder(src); - } - - // Checking of 'source' elements as per: - // http://www.whatwg.org/specs/web-apps/current-work/#pick-a - PRUint32 count = GetChildCount(); - for (PRUint32 i = 0; i < count; ++i) { - nsIContent* child = GetChildAt(i); - NS_ASSERTION(child, "GetChildCount lied!"); - - nsCOMPtr source = do_QueryInterface(child); - if (source) { - nsAutoString type; - nsAutoString src; - if (source->GetAttr(kNameSpaceID_None, nsGkAtoms::src, src) && - source->GetAttr(kNameSpaceID_None, nsGkAtoms::type, type) && - CreateDecoder(NS_ConvertUTF16toUTF8(type))) - return InitializeDecoder(src); - } - } - - return NS_ERROR_DOM_INVALID_STATE_ERR; -} - -nsresult nsHTMLMediaElement::InitializeDecoder(const nsAString& aURISpec) -{ - mNetworkState = nsIDOMHTMLMediaElement::LOADING; + *aURI = nsnull; nsCOMPtr doc = GetOwnerDoc(); if (!doc) { return NS_ERROR_DOM_INVALID_STATE_ERR; } - nsresult rv; - nsCOMPtr uri; - nsCOMPtr baseURL = GetBaseURI(); - rv = nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(uri), - aURISpec, - doc, - baseURL); + nsCOMPtr baseURI = GetBaseURI(); + nsresult rv = nsContentUtils::NewURIWithDocumentCharset(aURI, + aURISpec, + doc, + baseURI); NS_ENSURE_SUCCESS(rv, rv); + + PRBool equal; + if (aURISpec.IsEmpty() && + doc->GetDocumentURI() && + NS_SUCCEEDED(doc->GetDocumentURI()->Equals(*aURI, &equal)) && + equal) { + // It's not possible for a media resource to be embedded in the current + // document we extracted aURISpec from, so there's no point returning + // the current document URI just to let the caller attempt and fail to + // decode it. + NS_RELEASE(*aURI); + return NS_ERROR_DOM_INVALID_STATE_ERR; + } - if (mDecoder) { - mDecoder->ElementAvailable(this); - rv = mDecoder->Load(uri, nsnull, nsnull); - if (NS_FAILED(rv)) { - mDecoder->Shutdown(); - mDecoder = nsnull; + return NS_OK; +} + +nsresult nsHTMLMediaElement::PickMediaElement(nsIURI** aURI) +{ + NS_ENSURE_ARG_POINTER(aURI); + + // Implements: + // http://www.whatwg.org/specs/web-apps/current-work/#pick-a-media-resource + nsAutoString src; + if (GetAttr(kNameSpaceID_None, nsGkAtoms::src, src)) { + return NewURIFromString(src, aURI); + } + + // Checking of 'source' elements as per: + // http://www.whatwg.org/specs/web-apps/current-work/#pick-a-media-resource + PRUint32 count = GetChildCount(); + for (PRUint32 i = 0; i < count; ++i) { + nsIContent* child = GetChildAt(i); + NS_ASSERTION(child, "GetChildCount lied!"); + + nsCOMPtr source = do_QueryInterface(child); + if (source && + source->Tag() == nsGkAtoms::source && + source->IsNodeOfType(nsINode::eHTML)) { + nsAutoString type; + nsAutoString src; + if (source->GetAttr(kNameSpaceID_None, nsGkAtoms::src, src)) { + if (source->GetAttr(kNameSpaceID_None, nsGkAtoms::type, type)) { + if (CanDecode(NS_ConvertUTF16toUTF8(type))) { + return NewURIFromString(src, aURI); + } + } else if (i + 1 == count) { + // The last source element is permitted to omit the type + // attribute, in which case we need to open the URI and examine + // the channel's MIME type. + return NewURIFromString(src, aURI); + } + } } } - return rv; + + return NS_ERROR_DOM_INVALID_STATE_ERR; } void nsHTMLMediaElement::MetadataLoaded() @@ -887,6 +1016,10 @@ void nsHTMLMediaElement::DestroyContent() mDecoder->Shutdown(); mDecoder = nsnull; } + if (mChannel) { + mChannel->Cancel(NS_BINDING_ABORTED); + mChannel = nsnull; + } nsGenericHTMLElement::DestroyContent(); } diff --git a/content/media/video/test/Makefile.in b/content/media/video/test/Makefile.in index 483a183e081c..49618e794d2d 100644 --- a/content/media/video/test/Makefile.in +++ b/content/media/video/test/Makefile.in @@ -45,12 +45,14 @@ include $(topsrcdir)/config/rules.mk _TEST_FILES = test_autoplay.html \ test_bug461281.html \ + test_bug463162.xhtml \ test_constants.html \ test_controls.html \ test_currentTime.html \ test_duration1.html \ test_ended1.html \ test_ended2.html \ + test_media_selection.html \ test_networkState.html \ test_paused.html \ test_readyState.html \ diff --git a/content/media/video/test/test_bug463162.xhtml b/content/media/video/test/test_bug463162.xhtml new file mode 100644 index 000000000000..114ee60d30d9 --- /dev/null +++ b/content/media/video/test/test_bug463162.xhtml @@ -0,0 +1,31 @@ + + + + Test for Bug 463162 + + + + + + diff --git a/content/media/video/test/test_media_selection.html b/content/media/video/test/test_media_selection.html new file mode 100644 index 000000000000..ff609cc1f8b4 --- /dev/null +++ b/content/media/video/test/test_media_selection.html @@ -0,0 +1,143 @@ + + + + Media test: media selection + + + + + +
+
+
+ +