summaryrefslogtreecommitdiffstats
path: root/dom/media/MediaResource.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/MediaResource.cpp')
-rw-r--r--dom/media/MediaResource.cpp1648
1 files changed, 1648 insertions, 0 deletions
diff --git a/dom/media/MediaResource.cpp b/dom/media/MediaResource.cpp
new file mode 100644
index 000000000..d36783be7
--- /dev/null
+++ b/dom/media/MediaResource.cpp
@@ -0,0 +1,1648 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/DebugOnly.h"
+
+#include "MediaResource.h"
+#include "MediaResourceCallback.h"
+
+#include "mozilla/Mutex.h"
+#include "nsDebug.h"
+#include "nsNetUtil.h"
+#include "nsThreadUtils.h"
+#include "nsIFile.h"
+#include "nsIFileChannel.h"
+#include "nsIFileStreams.h"
+#include "nsIHttpChannel.h"
+#include "nsISeekableStream.h"
+#include "nsIInputStream.h"
+#include "nsIRequestObserver.h"
+#include "nsIStreamListener.h"
+#include "nsIScriptSecurityManager.h"
+#include "mozilla/dom/HTMLMediaElement.h"
+#include "nsError.h"
+#include "nsICachingChannel.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsContentUtils.h"
+#include "nsHostObjectProtocolHandler.h"
+#include <algorithm>
+#include "nsProxyRelease.h"
+#include "nsIContentPolicy.h"
+
+using mozilla::media::TimeUnit;
+
+mozilla::LazyLogModule gMediaResourceLog("MediaResource");
+#define RESOURCE_LOG(msg, ...) MOZ_LOG(gMediaResourceLog, mozilla::LogLevel::Debug, \
+ (msg, ##__VA_ARGS__))
+// Debug logging macro with object pointer and class name.
+#define CMLOG(msg, ...) \
+ RESOURCE_LOG("%p [ChannelMediaResource]: " msg, this, ##__VA_ARGS__)
+
+static const uint32_t HTTP_OK_CODE = 200;
+static const uint32_t HTTP_PARTIAL_RESPONSE_CODE = 206;
+
+namespace mozilla {
+
+void
+MediaResource::Destroy()
+{
+ // Ensures we only delete the MediaResource on the main thread.
+ if (NS_IsMainThread()) {
+ delete this;
+ return;
+ }
+ MOZ_ALWAYS_SUCCEEDS(
+ NS_DispatchToMainThread(NewNonOwningRunnableMethod(this, &MediaResource::Destroy)));
+}
+
+NS_IMPL_ADDREF(MediaResource)
+NS_IMPL_RELEASE_WITH_DESTROY(MediaResource, Destroy())
+NS_IMPL_QUERY_INTERFACE0(MediaResource)
+
+ChannelMediaResource::ChannelMediaResource(MediaResourceCallback* aCallback,
+ nsIChannel* aChannel,
+ nsIURI* aURI,
+ const nsACString& aContentType)
+ : BaseMediaResource(aCallback, aChannel, aURI, aContentType),
+ mOffset(0),
+ mReopenOnError(false),
+ mIgnoreClose(false),
+ mCacheStream(this),
+ mLock("ChannelMediaResource.mLock"),
+ mIgnoreResume(false),
+ mSuspendAgent(mChannel)
+{
+}
+
+ChannelMediaResource::~ChannelMediaResource()
+{
+ if (mListener) {
+ // Kill its reference to us since we're going away
+ mListener->Revoke();
+ }
+}
+
+// ChannelMediaResource::Listener just observes the channel and
+// forwards notifications to the ChannelMediaResource. We use multiple
+// listener objects so that when we open a new stream for a seek we can
+// disconnect the old listener from the ChannelMediaResource and hook up
+// a new listener, so notifications from the old channel are discarded
+// and don't confuse us.
+NS_IMPL_ISUPPORTS(ChannelMediaResource::Listener,
+ nsIRequestObserver, nsIStreamListener, nsIChannelEventSink,
+ nsIInterfaceRequestor)
+
+nsresult
+ChannelMediaResource::Listener::OnStartRequest(nsIRequest* aRequest,
+ nsISupports* aContext)
+{
+ if (!mResource)
+ return NS_OK;
+ return mResource->OnStartRequest(aRequest);
+}
+
+nsresult
+ChannelMediaResource::Listener::OnStopRequest(nsIRequest* aRequest,
+ nsISupports* aContext,
+ nsresult aStatus)
+{
+ if (!mResource)
+ return NS_OK;
+ return mResource->OnStopRequest(aRequest, aStatus);
+}
+
+nsresult
+ChannelMediaResource::Listener::OnDataAvailable(nsIRequest* aRequest,
+ nsISupports* aContext,
+ nsIInputStream* aStream,
+ uint64_t aOffset,
+ uint32_t aCount)
+{
+ if (!mResource)
+ return NS_OK;
+ return mResource->OnDataAvailable(aRequest, aStream, aCount);
+}
+
+nsresult
+ChannelMediaResource::Listener::AsyncOnChannelRedirect(nsIChannel* aOldChannel,
+ nsIChannel* aNewChannel,
+ uint32_t aFlags,
+ nsIAsyncVerifyRedirectCallback* cb)
+{
+ nsresult rv = NS_OK;
+ if (mResource)
+ rv = mResource->OnChannelRedirect(aOldChannel, aNewChannel, aFlags);
+
+ if (NS_FAILED(rv))
+ return rv;
+
+ cb->OnRedirectVerifyCallback(NS_OK);
+ return NS_OK;
+}
+
+nsresult
+ChannelMediaResource::Listener::GetInterface(const nsIID & aIID, void **aResult)
+{
+ return QueryInterface(aIID, aResult);
+}
+
+nsresult
+ChannelMediaResource::OnStartRequest(nsIRequest* aRequest)
+{
+ NS_ASSERTION(mChannel.get() == aRequest, "Wrong channel!");
+
+ MediaDecoderOwner* owner = mCallback->GetMediaOwner();
+ NS_ENSURE_TRUE(owner, NS_ERROR_FAILURE);
+ dom::HTMLMediaElement* element = owner->GetMediaElement();
+ NS_ENSURE_TRUE(element, NS_ERROR_FAILURE);
+ nsresult status;
+ nsresult rv = aRequest->GetStatus(&status);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (status == NS_BINDING_ABORTED) {
+ // Request was aborted before we had a chance to receive any data, or
+ // even an OnStartRequest(). Close the channel. This is important, as
+ // we don't want to mess up our state, as if we're cloned that would
+ // cause the clone to copy incorrect metadata (like whether we're
+ // infinite for example).
+ CloseChannel();
+ return status;
+ }
+
+ if (element->ShouldCheckAllowOrigin()) {
+ // If the request was cancelled by nsCORSListenerProxy due to failing
+ // the CORS security check, send an error through to the media element.
+ if (status == NS_ERROR_DOM_BAD_URI) {
+ mCallback->NotifyNetworkError();
+ return NS_ERROR_DOM_BAD_URI;
+ }
+ }
+
+ nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(aRequest);
+ bool seekable = false;
+ if (hc) {
+ uint32_t responseStatus = 0;
+ hc->GetResponseStatus(&responseStatus);
+ bool succeeded = false;
+ hc->GetRequestSucceeded(&succeeded);
+
+ if (!succeeded && NS_SUCCEEDED(status)) {
+ // HTTP-level error (e.g. 4xx); treat this as a fatal network-level error.
+ // We might get this on a seek.
+ // (Note that lower-level errors indicated by NS_FAILED(status) are
+ // handled in OnStopRequest.)
+ // A 416 error should treated as EOF here... it's possible
+ // that we don't get Content-Length, we read N bytes, then we
+ // suspend and resume, the resume reopens the channel and we seek to
+ // offset N, but there are no more bytes, so we get a 416
+ // "Requested Range Not Satisfiable".
+ if (responseStatus == HTTP_REQUESTED_RANGE_NOT_SATISFIABLE_CODE) {
+ // OnStopRequest will not be fired, so we need to do some of its
+ // work here.
+ mCacheStream.NotifyDataEnded(status);
+ } else {
+ mCallback->NotifyNetworkError();
+ }
+
+ // This disconnects our listener so we don't get any more data. We
+ // certainly don't want an error page to end up in our cache!
+ CloseChannel();
+ return NS_OK;
+ }
+
+ nsAutoCString ranges;
+ hc->GetResponseHeader(NS_LITERAL_CSTRING("Accept-Ranges"),
+ ranges);
+ bool acceptsRanges = ranges.EqualsLiteral("bytes");
+ // True if this channel will not return an unbounded amount of data
+ bool dataIsBounded = false;
+
+ int64_t contentLength = -1;
+ hc->GetContentLength(&contentLength);
+ if (contentLength >= 0 &&
+ (responseStatus == HTTP_OK_CODE ||
+ responseStatus == HTTP_PARTIAL_RESPONSE_CODE)) {
+ // "OK" status means Content-Length is for the whole resource.
+ // Since that's bounded, we know we have a finite-length resource.
+ dataIsBounded = true;
+ }
+
+ // Assume Range requests have a bounded upper limit unless the
+ // Content-Range header tells us otherwise.
+ bool boundedSeekLimit = true;
+ // Check response code for byte-range requests (seeking, chunk requests).
+ if (responseStatus == HTTP_PARTIAL_RESPONSE_CODE) {
+ // Parse Content-Range header.
+ int64_t rangeStart = 0;
+ int64_t rangeEnd = 0;
+ int64_t rangeTotal = 0;
+ rv = ParseContentRangeHeader(hc, rangeStart, rangeEnd, rangeTotal);
+
+ // We received 'Content-Range', so the server accepts range requests.
+ bool gotRangeHeader = NS_SUCCEEDED(rv);
+
+ if (gotRangeHeader) {
+ // We received 'Content-Range', so the server accepts range requests.
+ // Notify media cache about the length and start offset of data received.
+ // Note: If aRangeTotal == -1, then the total bytes is unknown at this stage.
+ // For now, tell the decoder that the stream is infinite.
+ if (rangeTotal == -1) {
+ boundedSeekLimit = false;
+ } else {
+ contentLength = std::max(contentLength, rangeTotal);
+ }
+ // Give some warnings if the ranges are unexpected.
+ // XXX These could be error conditions.
+ NS_WARNING_ASSERTION(
+ mOffset == rangeStart,
+ "response range start does not match current offset");
+ mOffset = rangeStart;
+ mCacheStream.NotifyDataStarted(rangeStart);
+ }
+ acceptsRanges = gotRangeHeader;
+ } else if (mOffset > 0 && responseStatus == HTTP_OK_CODE) {
+ // If we get an OK response but we were seeking, or requesting a byte
+ // range, then we have to assume that seeking doesn't work. We also need
+ // to tell the cache that it's getting data for the start of the stream.
+ mCacheStream.NotifyDataStarted(0);
+ mOffset = 0;
+
+ // The server claimed it supported range requests. It lied.
+ acceptsRanges = false;
+ }
+ if (mOffset == 0 && contentLength >= 0 &&
+ (responseStatus == HTTP_OK_CODE ||
+ responseStatus == HTTP_PARTIAL_RESPONSE_CODE)) {
+ mCacheStream.NotifyDataLength(contentLength);
+ }
+ // XXX we probably should examine the Content-Range header in case
+ // the server gave us a range which is not quite what we asked for
+
+ // If we get an HTTP_OK_CODE response to our byte range request,
+ // and the server isn't sending Accept-Ranges:bytes then we don't
+ // support seeking.
+ seekable = acceptsRanges;
+ if (seekable && boundedSeekLimit) {
+ // If range requests are supported, and we did not see an unbounded
+ // upper range limit, we assume the resource is bounded.
+ dataIsBounded = true;
+ }
+
+ mCallback->SetInfinite(!dataIsBounded);
+ }
+ mCacheStream.SetTransportSeekable(seekable);
+
+ {
+ MutexAutoLock lock(mLock);
+ mChannelStatistics->Start();
+ }
+
+ mReopenOnError = false;
+ mIgnoreClose = false;
+
+ mSuspendAgent.UpdateSuspendedStatusIfNeeded();
+
+ // Fires an initial progress event.
+ owner->DownloadProgressed();
+
+ return NS_OK;
+}
+
+bool
+ChannelMediaResource::IsTransportSeekable()
+{
+ return mCacheStream.IsTransportSeekable();
+}
+
+nsresult
+ChannelMediaResource::ParseContentRangeHeader(nsIHttpChannel * aHttpChan,
+ int64_t& aRangeStart,
+ int64_t& aRangeEnd,
+ int64_t& aRangeTotal)
+{
+ NS_ENSURE_ARG(aHttpChan);
+
+ nsAutoCString rangeStr;
+ nsresult rv = aHttpChan->GetResponseHeader(NS_LITERAL_CSTRING("Content-Range"),
+ rangeStr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_FALSE(rangeStr.IsEmpty(), NS_ERROR_ILLEGAL_VALUE);
+
+ // Parse the range header: e.g. Content-Range: bytes 7000-7999/8000.
+ int32_t spacePos = rangeStr.Find(NS_LITERAL_CSTRING(" "));
+ int32_t dashPos = rangeStr.Find(NS_LITERAL_CSTRING("-"), true, spacePos);
+ int32_t slashPos = rangeStr.Find(NS_LITERAL_CSTRING("/"), true, dashPos);
+
+ nsAutoCString aRangeStartText;
+ rangeStr.Mid(aRangeStartText, spacePos+1, dashPos-(spacePos+1));
+ aRangeStart = aRangeStartText.ToInteger64(&rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(0 <= aRangeStart, NS_ERROR_ILLEGAL_VALUE);
+
+ nsAutoCString aRangeEndText;
+ rangeStr.Mid(aRangeEndText, dashPos+1, slashPos-(dashPos+1));
+ aRangeEnd = aRangeEndText.ToInteger64(&rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(aRangeStart < aRangeEnd, NS_ERROR_ILLEGAL_VALUE);
+
+ nsAutoCString aRangeTotalText;
+ rangeStr.Right(aRangeTotalText, rangeStr.Length()-(slashPos+1));
+ if (aRangeTotalText[0] == '*') {
+ aRangeTotal = -1;
+ } else {
+ aRangeTotal = aRangeTotalText.ToInteger64(&rv);
+ NS_ENSURE_TRUE(aRangeEnd < aRangeTotal, NS_ERROR_ILLEGAL_VALUE);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ CMLOG("Received bytes [%lld] to [%lld] of [%lld] for decoder[%p]",
+ aRangeStart, aRangeEnd, aRangeTotal, mCallback.get());
+
+ return NS_OK;
+}
+
+nsresult
+ChannelMediaResource::OnStopRequest(nsIRequest* aRequest, nsresult aStatus)
+{
+ NS_ASSERTION(mChannel.get() == aRequest, "Wrong channel!");
+ NS_ASSERTION(!mSuspendAgent.IsSuspended(),
+ "How can OnStopRequest fire while we're suspended?");
+
+ {
+ MutexAutoLock lock(mLock);
+ mChannelStatistics->Stop();
+ }
+
+ // Note that aStatus might have succeeded --- this might be a normal close
+ // --- even in situations where the server cut us off because we were
+ // suspended. So we need to "reopen on error" in that case too. The only
+ // cases where we don't need to reopen are when *we* closed the stream.
+ // But don't reopen if we need to seek and we don't think we can... that would
+ // cause us to just re-read the stream, which would be really bad.
+ if (mReopenOnError &&
+ aStatus != NS_ERROR_PARSED_DATA_CACHED && aStatus != NS_BINDING_ABORTED &&
+ (mOffset == 0 || mCacheStream.IsTransportSeekable())) {
+ // If the stream did close normally, then if the server is seekable we'll
+ // just seek to the end of the resource and get an HTTP 416 error because
+ // there's nothing there, so this isn't bad.
+ nsresult rv = CacheClientSeek(mOffset, false);
+ if (NS_SUCCEEDED(rv))
+ return rv;
+ // If the reopen/reseek fails, just fall through and treat this
+ // error as fatal.
+ }
+
+ if (!mIgnoreClose) {
+ mCacheStream.NotifyDataEnded(aStatus);
+
+ // Move this request back into the foreground. This is necessary for
+ // requests owned by video documents to ensure the load group fires
+ // OnStopRequest when restoring from session history.
+ nsLoadFlags loadFlags;
+ DebugOnly<nsresult> rv = mChannel->GetLoadFlags(&loadFlags);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "GetLoadFlags() failed!");
+
+ if (loadFlags & nsIRequest::LOAD_BACKGROUND) {
+ ModifyLoadFlags(loadFlags & ~nsIRequest::LOAD_BACKGROUND);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+ChannelMediaResource::OnChannelRedirect(nsIChannel* aOld, nsIChannel* aNew,
+ uint32_t aFlags)
+{
+ mChannel = aNew;
+ mSuspendAgent.NotifyChannelOpened(mChannel);
+ return SetupChannelHeaders();
+}
+
+struct CopySegmentClosure {
+ nsCOMPtr<nsIPrincipal> mPrincipal;
+ ChannelMediaResource* mResource;
+};
+
+nsresult
+ChannelMediaResource::CopySegmentToCache(nsIInputStream *aInStream,
+ void *aClosure,
+ const char *aFromSegment,
+ uint32_t aToOffset,
+ uint32_t aCount,
+ uint32_t *aWriteCount)
+{
+ CopySegmentClosure* closure = static_cast<CopySegmentClosure*>(aClosure);
+
+ closure->mResource->mCallback->NotifyDataArrived();
+
+ // Keep track of where we're up to.
+ RESOURCE_LOG("%p [ChannelMediaResource]: CopySegmentToCache at mOffset [%lld] add "
+ "[%d] bytes for decoder[%p]",
+ closure->mResource, closure->mResource->mOffset, aCount,
+ closure->mResource->mCallback.get());
+ closure->mResource->mOffset += aCount;
+
+ closure->mResource->mCacheStream.NotifyDataReceived(aCount, aFromSegment,
+ closure->mPrincipal);
+ *aWriteCount = aCount;
+ return NS_OK;
+}
+
+nsresult
+ChannelMediaResource::OnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* aStream,
+ uint32_t aCount)
+{
+ NS_ASSERTION(mChannel.get() == aRequest, "Wrong channel!");
+
+ {
+ MutexAutoLock lock(mLock);
+ mChannelStatistics->AddBytes(aCount);
+ }
+
+ CopySegmentClosure closure;
+ nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
+ if (secMan && mChannel) {
+ secMan->GetChannelResultPrincipal(mChannel, getter_AddRefs(closure.mPrincipal));
+ }
+ closure.mResource = this;
+
+ uint32_t count = aCount;
+ while (count > 0) {
+ uint32_t read;
+ nsresult rv = aStream->ReadSegments(CopySegmentToCache, &closure, count,
+ &read);
+ if (NS_FAILED(rv))
+ return rv;
+ NS_ASSERTION(read > 0, "Read 0 bytes while data was available?");
+ count -= read;
+ }
+
+ return NS_OK;
+}
+
+nsresult ChannelMediaResource::Open(nsIStreamListener **aStreamListener)
+{
+ NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
+
+ if (!mChannelStatistics) {
+ mChannelStatistics = new MediaChannelStatistics();
+ }
+
+ nsresult rv = mCacheStream.Init();
+ if (NS_FAILED(rv))
+ return rv;
+ NS_ASSERTION(mOffset == 0, "Who set mOffset already?");
+
+ if (!mChannel) {
+ // When we're a clone, the decoder might ask us to Open even though
+ // we haven't established an mChannel (because we might not need one)
+ NS_ASSERTION(!aStreamListener,
+ "Should have already been given a channel if we're to return a stream listener");
+ return NS_OK;
+ }
+
+ return OpenChannel(aStreamListener);
+}
+
+nsresult ChannelMediaResource::OpenChannel(nsIStreamListener** aStreamListener)
+{
+ NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
+ NS_ENSURE_TRUE(mChannel, NS_ERROR_NULL_POINTER);
+ NS_ASSERTION(!mListener, "Listener should have been removed by now");
+
+ if (aStreamListener) {
+ *aStreamListener = nullptr;
+ }
+
+ // Set the content length, if it's available as an HTTP header.
+ // This ensures that MediaResource wrapping objects for platform libraries
+ // that expect to know the length of a resource can get it before
+ // OnStartRequest() fires.
+ nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(mChannel);
+ if (hc) {
+ int64_t cl = -1;
+ if (NS_SUCCEEDED(hc->GetContentLength(&cl)) && cl != -1) {
+ mCacheStream.NotifyDataLength(cl);
+ }
+ }
+
+ mListener = new Listener(this);
+ if (aStreamListener) {
+ *aStreamListener = mListener;
+ NS_ADDREF(*aStreamListener);
+ } else {
+ nsresult rv = mChannel->SetNotificationCallbacks(mListener.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = SetupChannelHeaders();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mChannel->AsyncOpen2(mListener);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Tell the media element that we are fetching data from a channel.
+ MediaDecoderOwner* owner = mCallback->GetMediaOwner();
+ NS_ENSURE_TRUE(owner, NS_ERROR_FAILURE);
+ dom::HTMLMediaElement* element = owner->GetMediaElement();
+ element->DownloadResumed(true);
+ }
+
+ return NS_OK;
+}
+
+nsresult ChannelMediaResource::SetupChannelHeaders()
+{
+ // Always use a byte range request even if we're reading 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) {
+ // Use |mOffset| if seeking in a complete file download.
+ nsAutoCString rangeString("bytes=");
+ rangeString.AppendInt(mOffset);
+ rangeString.Append('-');
+ nsresult rv = hc->SetRequestHeader(NS_LITERAL_CSTRING("Range"), rangeString, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Send Accept header for video and audio types only (Bug 489071)
+ NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread");
+ MediaDecoderOwner* owner = mCallback->GetMediaOwner();
+ NS_ENSURE_TRUE(owner, NS_ERROR_FAILURE);
+ dom::HTMLMediaElement* element = owner->GetMediaElement();
+ NS_ENSURE_TRUE(element, NS_ERROR_FAILURE);
+ element->SetRequestHeaders(hc);
+ } else {
+ NS_ASSERTION(mOffset == 0, "Don't know how to seek on this channel type");
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+nsresult ChannelMediaResource::Close()
+{
+ NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
+
+ mCacheStream.Close();
+ CloseChannel();
+ return NS_OK;
+}
+
+already_AddRefed<nsIPrincipal> ChannelMediaResource::GetCurrentPrincipal()
+{
+ NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
+
+ nsCOMPtr<nsIPrincipal> principal = mCacheStream.GetCurrentPrincipal();
+ return principal.forget();
+}
+
+bool ChannelMediaResource::CanClone()
+{
+ return mCacheStream.IsAvailableForSharing();
+}
+
+already_AddRefed<MediaResource> ChannelMediaResource::CloneData(MediaResourceCallback* aCallback)
+{
+ NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
+ NS_ASSERTION(mCacheStream.IsAvailableForSharing(), "Stream can't be cloned");
+
+ RefPtr<ChannelMediaResource> resource =
+ new ChannelMediaResource(aCallback,
+ nullptr,
+ mURI,
+ GetContentType());
+ if (resource) {
+ // Initially the clone is treated as suspended by the cache, because
+ // we don't have a channel. If the cache needs to read data from the clone
+ // it will call CacheClientResume (or CacheClientSeek with aResume true)
+ // which will recreate the channel. This way, if all of the media data
+ // is already in the cache we don't create an unnecessary HTTP channel
+ // and perform a useless HTTP transaction.
+ resource->mSuspendAgent.Suspend();
+ resource->mCacheStream.InitAsClone(&mCacheStream);
+ resource->mChannelStatistics = new MediaChannelStatistics(mChannelStatistics);
+ resource->mChannelStatistics->Stop();
+ }
+ return resource.forget();
+}
+
+void ChannelMediaResource::CloseChannel()
+{
+ NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
+
+ {
+ MutexAutoLock lock(mLock);
+ mChannelStatistics->Stop();
+ }
+
+ if (mListener) {
+ mListener->Revoke();
+ mListener = nullptr;
+ }
+
+ if (mChannel) {
+ mSuspendAgent.NotifyChannelClosing();
+ // The status we use here won't be passed to the decoder, since
+ // we've already revoked the listener. It can however be passed
+ // to nsDocumentViewer::LoadComplete if our channel is the one
+ // that kicked off creation of a video document. We don't want that
+ // document load to think there was an error.
+ // NS_ERROR_PARSED_DATA_CACHED is the best thing we have for that
+ // at the moment.
+ mChannel->Cancel(NS_ERROR_PARSED_DATA_CACHED);
+ mChannel = nullptr;
+ }
+}
+
+nsresult ChannelMediaResource::ReadFromCache(char* aBuffer,
+ int64_t aOffset,
+ uint32_t aCount)
+{
+ return mCacheStream.ReadFromCache(aBuffer, aOffset, aCount);
+}
+
+nsresult ChannelMediaResource::ReadAt(int64_t aOffset,
+ char* aBuffer,
+ uint32_t aCount,
+ uint32_t* aBytes)
+{
+ NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
+
+ nsresult rv = mCacheStream.ReadAt(aOffset, aBuffer, aCount, aBytes);
+ if (NS_SUCCEEDED(rv)) {
+ DispatchBytesConsumed(*aBytes, aOffset);
+ }
+ return rv;
+}
+
+already_AddRefed<MediaByteBuffer>
+ChannelMediaResource::MediaReadAt(int64_t aOffset, uint32_t aCount)
+{
+ NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
+
+ RefPtr<MediaByteBuffer> bytes = new MediaByteBuffer();
+ bool ok = bytes->SetLength(aCount, fallible);
+ NS_ENSURE_TRUE(ok, nullptr);
+ char* curr = reinterpret_cast<char*>(bytes->Elements());
+ const char* start = curr;
+ while (aCount > 0) {
+ uint32_t bytesRead;
+ nsresult rv = mCacheStream.ReadAt(aOffset, curr, aCount, &bytesRead);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+ if (!bytesRead) {
+ break;
+ }
+ aOffset += bytesRead;
+ aCount -= bytesRead;
+ curr += bytesRead;
+ }
+ bytes->SetLength(curr - start);
+ return bytes.forget();
+}
+
+int64_t ChannelMediaResource::Tell()
+{
+ return mCacheStream.Tell();
+}
+
+nsresult ChannelMediaResource::GetCachedRanges(MediaByteRangeSet& aRanges)
+{
+ return mCacheStream.GetCachedRanges(aRanges);
+}
+
+void ChannelMediaResource::Suspend(bool aCloseImmediately)
+{
+ NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread");
+
+ MediaDecoderOwner* owner = mCallback->GetMediaOwner();
+ if (!owner) {
+ // Shutting down; do nothing.
+ return;
+ }
+ dom::HTMLMediaElement* element = owner->GetMediaElement();
+ if (!element) {
+ // Shutting down; do nothing.
+ return;
+ }
+
+ if (mChannel && aCloseImmediately && mCacheStream.IsTransportSeekable()) {
+ // Kill off our channel right now, but don't tell anyone about it.
+ mIgnoreClose = true;
+ CloseChannel();
+ element->DownloadSuspended();
+ }
+
+ if (mSuspendAgent.Suspend()) {
+ if (mChannel) {
+ {
+ MutexAutoLock lock(mLock);
+ mChannelStatistics->Stop();
+ }
+ element->DownloadSuspended();
+ }
+ }
+}
+
+void ChannelMediaResource::Resume()
+{
+ NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread");
+
+ MediaDecoderOwner* owner = mCallback->GetMediaOwner();
+ if (!owner) {
+ // Shutting down; do nothing.
+ return;
+ }
+ dom::HTMLMediaElement* element = owner->GetMediaElement();
+ if (!element) {
+ // Shutting down; do nothing.
+ return;
+ }
+
+ if (mSuspendAgent.Resume()) {
+ if (mChannel) {
+ // Just wake up our existing channel
+ {
+ MutexAutoLock lock(mLock);
+ mChannelStatistics->Start();
+ }
+ // if an error occurs after Resume, assume it's because the server
+ // timed out the connection and we should reopen it.
+ mReopenOnError = true;
+ element->DownloadResumed();
+ } else {
+ int64_t totalLength = mCacheStream.GetLength();
+ // If mOffset is at the end of the stream, then we shouldn't try to
+ // seek to it. The seek will fail and be wasted anyway. We can leave
+ // the channel dead; if the media cache wants to read some other data
+ // in the future, it will call CacheClientSeek itself which will reopen the
+ // channel.
+ if (totalLength < 0 || mOffset < totalLength) {
+ // There is (or may be) data to read at mOffset, so start reading it.
+ // Need to recreate the channel.
+ CacheClientSeek(mOffset, false);
+ element->DownloadResumed();
+ } else {
+ // The channel remains dead. Do not notify DownloadResumed() which
+ // will leave the media element in NETWORK_LOADING state.
+ }
+ }
+ }
+}
+
+nsresult
+ChannelMediaResource::RecreateChannel()
+{
+ nsLoadFlags loadFlags =
+ nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE_IF_BUSY |
+ nsIChannel::LOAD_CLASSIFY_URI |
+ (mLoadInBackground ? nsIRequest::LOAD_BACKGROUND : 0);
+
+ MediaDecoderOwner* owner = mCallback->GetMediaOwner();
+ if (!owner) {
+ // The decoder is being shut down, so don't bother opening a new channel
+ return NS_OK;
+ }
+ dom::HTMLMediaElement* element = owner->GetMediaElement();
+ if (!element) {
+ // The decoder is being shut down, so don't bother opening a new channel
+ return NS_OK;
+ }
+ nsCOMPtr<nsILoadGroup> loadGroup = element->GetDocumentLoadGroup();
+ NS_ENSURE_TRUE(loadGroup, NS_ERROR_NULL_POINTER);
+
+ nsSecurityFlags securityFlags = element->ShouldCheckAllowOrigin()
+ ? nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS
+ : nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS;
+
+ MOZ_ASSERT(element->IsAnyOfHTMLElements(nsGkAtoms::audio, nsGkAtoms::video));
+ nsContentPolicyType contentPolicyType = element->IsHTMLElement(nsGkAtoms::audio) ?
+ nsIContentPolicy::TYPE_INTERNAL_AUDIO : nsIContentPolicy::TYPE_INTERNAL_VIDEO;
+
+ nsresult rv = NS_NewChannel(getter_AddRefs(mChannel),
+ mURI,
+ element,
+ securityFlags,
+ contentPolicyType,
+ loadGroup,
+ nullptr, // aCallbacks
+ loadFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We have cached the Content-Type, which should not change. Give a hint to
+ // the channel to avoid a sniffing failure, which would be expected because we
+ // are probably seeking in the middle of the bitstream, and sniffing relies
+ // on the presence of a magic number at the beginning of the stream.
+ NS_ASSERTION(!GetContentType().IsEmpty(),
+ "When recreating a channel, we should know the Content-Type.");
+ mChannel->SetContentType(GetContentType());
+ mSuspendAgent.NotifyChannelOpened(mChannel);
+
+ // Tell the cache to reset the download status when the channel is reopened.
+ mCacheStream.NotifyChannelRecreated();
+
+ return rv;
+}
+
+void
+ChannelMediaResource::DoNotifyDataReceived()
+{
+ mDataReceivedEvent.Revoke();
+ mCallback->NotifyBytesDownloaded();
+}
+
+void
+ChannelMediaResource::CacheClientNotifyDataReceived()
+{
+ NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread");
+ // NOTE: this can be called with the media cache lock held, so don't
+ // block or do anything which might try to acquire a lock!
+
+ if (mDataReceivedEvent.IsPending())
+ return;
+
+ mDataReceivedEvent =
+ NewNonOwningRunnableMethod(this, &ChannelMediaResource::DoNotifyDataReceived);
+ NS_DispatchToMainThread(mDataReceivedEvent.get());
+}
+
+void
+ChannelMediaResource::CacheClientNotifyDataEnded(nsresult aStatus)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ mCallback->NotifyDataEnded(aStatus);
+}
+
+void
+ChannelMediaResource::CacheClientNotifyPrincipalChanged()
+{
+ NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread");
+
+ mCallback->NotifyPrincipalChanged();
+}
+
+void
+ChannelMediaResource::CacheClientNotifySuspendedStatusChanged()
+{
+ NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread");
+ mCallback->NotifySuspendedStatusChanged();
+}
+
+nsresult
+ChannelMediaResource::CacheClientSeek(int64_t aOffset, bool aResume)
+{
+ NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread");
+
+ CMLOG("CacheClientSeek requested for aOffset [%lld] for decoder [%p]",
+ aOffset, mCallback.get());
+
+ CloseChannel();
+
+ mOffset = aOffset;
+
+ // Don't report close of the channel because the channel is not closed for
+ // download ended, but for internal changes in the read position.
+ mIgnoreClose = true;
+
+ if (aResume) {
+ mSuspendAgent.Resume();
+ }
+
+ // Don't create a new channel if we are still suspended. The channel will
+ // be recreated when we are resumed.
+ if (mSuspendAgent.IsSuspended()) {
+ return NS_OK;
+ }
+
+ nsresult rv = RecreateChannel();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return OpenChannel(nullptr);
+}
+
+void
+ChannelMediaResource::FlushCache()
+{
+ NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
+
+ // Ensure that data in the cache's partial block is written to disk.
+ mCacheStream.FlushPartialBlock();
+}
+
+void
+ChannelMediaResource::NotifyLastByteRange()
+{
+ NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
+
+ // Tell media cache that the last data has been downloaded.
+ // Note: subsequent seeks will require re-opening the channel etc.
+ mCacheStream.NotifyDataEnded(NS_OK);
+
+}
+
+nsresult
+ChannelMediaResource::CacheClientSuspend()
+{
+ Suspend(false);
+ return NS_OK;
+}
+
+nsresult
+ChannelMediaResource::CacheClientResume()
+{
+ Resume();
+ return NS_OK;
+}
+
+int64_t
+ChannelMediaResource::GetNextCachedData(int64_t aOffset)
+{
+ return mCacheStream.GetNextCachedData(aOffset);
+}
+
+int64_t
+ChannelMediaResource::GetCachedDataEnd(int64_t aOffset)
+{
+ return mCacheStream.GetCachedDataEnd(aOffset);
+}
+
+bool
+ChannelMediaResource::IsDataCachedToEndOfResource(int64_t aOffset)
+{
+ return mCacheStream.IsDataCachedToEndOfStream(aOffset);
+}
+
+void
+ChannelMediaResource::EnsureCacheUpToDate()
+{
+ mCacheStream.EnsureCacheUpdate();
+}
+
+bool
+ChannelMediaResource::IsSuspendedByCache()
+{
+ return mCacheStream.AreAllStreamsForResourceSuspended();
+}
+
+bool
+ChannelMediaResource::IsSuspended()
+{
+ return mSuspendAgent.IsSuspended();
+}
+
+void
+ChannelMediaResource::SetReadMode(MediaCacheStream::ReadMode aMode)
+{
+ mCacheStream.SetReadMode(aMode);
+}
+
+void
+ChannelMediaResource::SetPlaybackRate(uint32_t aBytesPerSecond)
+{
+ mCacheStream.SetPlaybackRate(aBytesPerSecond);
+}
+
+void
+ChannelMediaResource::Pin()
+{
+ mCacheStream.Pin();
+}
+
+void
+ChannelMediaResource::Unpin()
+{
+ mCacheStream.Unpin();
+}
+
+double
+ChannelMediaResource::GetDownloadRate(bool* aIsReliable)
+{
+ MutexAutoLock lock(mLock);
+ return mChannelStatistics->GetRate(aIsReliable);
+}
+
+int64_t
+ChannelMediaResource::GetLength()
+{
+ return mCacheStream.GetLength();
+}
+
+// ChannelSuspendAgent
+
+bool
+ChannelSuspendAgent::Suspend()
+{
+ SuspendInternal();
+ return (++mSuspendCount == 1);
+}
+
+void
+ChannelSuspendAgent::SuspendInternal()
+{
+ if (mChannel) {
+ bool isPending = false;
+ nsresult rv = mChannel->IsPending(&isPending);
+ if (NS_SUCCEEDED(rv) && isPending && !mIsChannelSuspended) {
+ mChannel->Suspend();
+ mIsChannelSuspended = true;
+ }
+ }
+}
+
+bool
+ChannelSuspendAgent::Resume()
+{
+ MOZ_ASSERT(IsSuspended(), "Resume without suspend!");
+ --mSuspendCount;
+
+ if (mSuspendCount == 0) {
+ if (mChannel && mIsChannelSuspended) {
+ mChannel->Resume();
+ mIsChannelSuspended = false;
+ }
+ return true;
+ }
+ return false;
+}
+
+void
+ChannelSuspendAgent::UpdateSuspendedStatusIfNeeded()
+{
+ if (!mIsChannelSuspended && IsSuspended()) {
+ SuspendInternal();
+ }
+}
+
+void
+ChannelSuspendAgent::NotifyChannelOpened(nsIChannel* aChannel)
+{
+ MOZ_ASSERT(aChannel);
+ mChannel = aChannel;
+}
+
+void
+ChannelSuspendAgent::NotifyChannelClosing()
+{
+ MOZ_ASSERT(mChannel);
+ // Before close the channel, it need to be resumed to make sure its internal
+ // state is correct. Besides, We need to suspend the channel after recreating.
+ if (mIsChannelSuspended) {
+ mChannel->Resume();
+ mIsChannelSuspended = false;
+ }
+ mChannel = nullptr;
+}
+
+bool
+ChannelSuspendAgent::IsSuspended()
+{
+ return (mSuspendCount > 0);
+}
+
+// FileMediaResource
+
+class FileMediaResource : public BaseMediaResource
+{
+public:
+ FileMediaResource(MediaResourceCallback* aCallback,
+ nsIChannel* aChannel,
+ nsIURI* aURI,
+ const nsACString& aContentType) :
+ BaseMediaResource(aCallback, aChannel, aURI, aContentType),
+ mSize(-1),
+ mLock("FileMediaResource.mLock"),
+ mSizeInitialized(false)
+ {
+ }
+ ~FileMediaResource()
+ {
+ }
+
+ // Main thread
+ nsresult Open(nsIStreamListener** aStreamListener) override;
+ nsresult Close() override;
+ void Suspend(bool aCloseImmediately) override {}
+ void Resume() override {}
+ already_AddRefed<nsIPrincipal> GetCurrentPrincipal() override;
+ bool CanClone() override;
+ already_AddRefed<MediaResource> CloneData(MediaResourceCallback* aCallback) override;
+ nsresult ReadFromCache(char* aBuffer, int64_t aOffset, uint32_t aCount) override;
+
+ // These methods are called off the main thread.
+
+ // Other thread
+ void SetReadMode(MediaCacheStream::ReadMode aMode) override {}
+ void SetPlaybackRate(uint32_t aBytesPerSecond) override {}
+ nsresult ReadAt(int64_t aOffset, char* aBuffer,
+ uint32_t aCount, uint32_t* aBytes) override;
+ already_AddRefed<MediaByteBuffer> MediaReadAt(int64_t aOffset, uint32_t aCount) override;
+ int64_t Tell() override;
+
+ // Any thread
+ void Pin() override {}
+ void Unpin() override {}
+ double GetDownloadRate(bool* aIsReliable) override
+ {
+ // The data's all already here
+ *aIsReliable = true;
+ return 100*1024*1024; // arbitray, use 100MB/s
+ }
+ int64_t GetLength() override {
+ MutexAutoLock lock(mLock);
+
+ EnsureSizeInitialized();
+ return mSizeInitialized ? mSize : 0;
+ }
+ int64_t GetNextCachedData(int64_t aOffset) override
+ {
+ MutexAutoLock lock(mLock);
+
+ EnsureSizeInitialized();
+ return (aOffset < mSize) ? aOffset : -1;
+ }
+ int64_t GetCachedDataEnd(int64_t aOffset) override {
+ MutexAutoLock lock(mLock);
+
+ EnsureSizeInitialized();
+ return std::max(aOffset, mSize);
+ }
+ bool IsDataCachedToEndOfResource(int64_t aOffset) override { return true; }
+ bool IsSuspendedByCache() override { return true; }
+ bool IsSuspended() override { return true; }
+ bool IsTransportSeekable() override { return true; }
+
+ nsresult GetCachedRanges(MediaByteRangeSet& aRanges) override;
+
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override
+ {
+ // Might be useful to track in the future:
+ // - mInput
+ return BaseMediaResource::SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override
+ {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+protected:
+ // These Unsafe variants of Read and Seek perform their operations
+ // without acquiring mLock. The caller must obtain the lock before
+ // calling. The implmentation of Read, Seek and ReadAt obtains the
+ // lock before calling these Unsafe variants to read or seek.
+ nsresult UnsafeRead(char* aBuffer, uint32_t aCount, uint32_t* aBytes);
+ nsresult UnsafeSeek(int32_t aWhence, int64_t aOffset);
+private:
+ // Ensures mSize is initialized, if it can be.
+ // mLock must be held when this is called, and mInput must be non-null.
+ void EnsureSizeInitialized();
+ already_AddRefed<MediaByteBuffer> UnsafeMediaReadAt(
+ int64_t aOffset, uint32_t aCount);
+
+ // The file size, or -1 if not known. Immutable after Open().
+ // Can be used from any thread.
+ int64_t mSize;
+
+ // This lock handles synchronisation between calls to Close() and
+ // the Read, Seek, etc calls. Close must not be called while a
+ // Read or Seek is in progress since it resets various internal
+ // values to null.
+ // This lock protects mSeekable, mInput, mSize, and mSizeInitialized.
+ Mutex mLock;
+
+ // Seekable stream interface to file. This can be used from any
+ // thread.
+ nsCOMPtr<nsISeekableStream> mSeekable;
+
+ // Input stream for the media data. This can be used from any
+ // thread.
+ nsCOMPtr<nsIInputStream> mInput;
+
+ // Whether we've attempted to initialize mSize. Note that mSize can be -1
+ // when mSizeInitialized is true if we tried and failed to get the size
+ // of the file.
+ bool mSizeInitialized;
+};
+
+void FileMediaResource::EnsureSizeInitialized()
+{
+ mLock.AssertCurrentThreadOwns();
+ NS_ASSERTION(mInput, "Must have file input stream");
+ if (mSizeInitialized) {
+ return;
+ }
+ mSizeInitialized = true;
+ // Get the file size and inform the decoder.
+ uint64_t size;
+ nsresult res = mInput->Available(&size);
+ if (NS_SUCCEEDED(res) && size <= INT64_MAX) {
+ mSize = (int64_t)size;
+ mCallback->NotifyDataEnded(NS_OK);
+ }
+}
+
+nsresult FileMediaResource::GetCachedRanges(MediaByteRangeSet& aRanges)
+{
+ MutexAutoLock lock(mLock);
+
+ EnsureSizeInitialized();
+ if (mSize == -1) {
+ return NS_ERROR_FAILURE;
+ }
+ aRanges += MediaByteRange(0, mSize);
+ return NS_OK;
+}
+
+nsresult FileMediaResource::Open(nsIStreamListener** aStreamListener)
+{
+ NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
+
+ if (aStreamListener) {
+ *aStreamListener = nullptr;
+ }
+
+ nsresult rv = NS_OK;
+ if (aStreamListener) {
+ // The channel is already open. We need a synchronous stream that
+ // implements nsISeekableStream, so we have to find the underlying
+ // file and reopen it
+ nsCOMPtr<nsIFileChannel> fc(do_QueryInterface(mChannel));
+ if (fc) {
+ nsCOMPtr<nsIFile> file;
+ rv = fc->GetFile(getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = NS_NewLocalFileInputStream(
+ getter_AddRefs(mInput), file, -1, -1, nsIFileInputStream::SHARE_DELETE);
+ } else if (IsBlobURI(mURI)) {
+ rv = NS_GetStreamForBlobURI(mURI, getter_AddRefs(mInput));
+ }
+ } else {
+ rv = mChannel->Open2(getter_AddRefs(mInput));
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mSeekable = do_QueryInterface(mInput);
+ if (!mSeekable) {
+ // XXX The file may just be a .url or similar
+ // shortcut that points to a Web site. We need to fix this by
+ // doing an async open and waiting until we locate the real resource,
+ // then using that (if it's still a file!).
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+nsresult FileMediaResource::Close()
+{
+ NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
+
+ // Since mChennel is only accessed by main thread, there is no necessary to
+ // take the lock.
+ if (mChannel) {
+ mChannel->Cancel(NS_ERROR_PARSED_DATA_CACHED);
+ mChannel = nullptr;
+ }
+
+ return NS_OK;
+}
+
+already_AddRefed<nsIPrincipal> FileMediaResource::GetCurrentPrincipal()
+{
+ NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
+
+ nsCOMPtr<nsIPrincipal> principal;
+ nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
+ if (!secMan || !mChannel)
+ return nullptr;
+ secMan->GetChannelResultPrincipal(mChannel, getter_AddRefs(principal));
+ return principal.forget();
+}
+
+bool FileMediaResource::CanClone()
+{
+ return true;
+}
+
+already_AddRefed<MediaResource> FileMediaResource::CloneData(MediaResourceCallback* aCallback)
+{
+ NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
+
+ MediaDecoderOwner* owner = mCallback->GetMediaOwner();
+ if (!owner) {
+ // The decoder is being shut down, so we can't clone
+ return nullptr;
+ }
+ dom::HTMLMediaElement* element = owner->GetMediaElement();
+ if (!element) {
+ // The decoder is being shut down, so we can't clone
+ return nullptr;
+ }
+ nsCOMPtr<nsILoadGroup> loadGroup = element->GetDocumentLoadGroup();
+ NS_ENSURE_TRUE(loadGroup, nullptr);
+
+ MOZ_ASSERT(element->IsAnyOfHTMLElements(nsGkAtoms::audio, nsGkAtoms::video));
+ nsContentPolicyType contentPolicyType = element->IsHTMLElement(nsGkAtoms::audio) ?
+ nsIContentPolicy::TYPE_INTERNAL_AUDIO : nsIContentPolicy::TYPE_INTERNAL_VIDEO;
+
+ nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL | nsIChannel::LOAD_CLASSIFY_URI;
+
+ nsCOMPtr<nsIChannel> channel;
+ nsresult rv =
+ NS_NewChannel(getter_AddRefs(channel),
+ mURI,
+ element,
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS,
+ contentPolicyType,
+ loadGroup,
+ nullptr, // aCallbacks
+ loadFlags);
+
+ if (NS_FAILED(rv))
+ return nullptr;
+
+ RefPtr<MediaResource> resource(new FileMediaResource(aCallback, channel, mURI, GetContentType()));
+ return resource.forget();
+}
+
+nsresult FileMediaResource::ReadFromCache(char* aBuffer, int64_t aOffset, uint32_t aCount)
+{
+ MutexAutoLock lock(mLock);
+
+ EnsureSizeInitialized();
+ if (!aCount) {
+ return NS_OK;
+ }
+ int64_t offset = 0;
+ nsresult res = mSeekable->Tell(&offset);
+ NS_ENSURE_SUCCESS(res,res);
+ res = mSeekable->Seek(nsISeekableStream::NS_SEEK_SET, aOffset);
+ NS_ENSURE_SUCCESS(res,res);
+ uint32_t bytesRead = 0;
+ do {
+ uint32_t x = 0;
+ uint32_t bytesToRead = aCount - bytesRead;
+ res = mInput->Read(aBuffer, bytesToRead, &x);
+ bytesRead += x;
+ if (!x) {
+ res = NS_ERROR_FAILURE;
+ }
+ } while (bytesRead != aCount && res == NS_OK);
+
+ // Reset read head to original position so we don't disturb any other
+ // reading thread.
+ nsresult seekres = mSeekable->Seek(nsISeekableStream::NS_SEEK_SET, offset);
+
+ // If a read failed in the loop above, we want to return its failure code.
+ NS_ENSURE_SUCCESS(res,res);
+
+ // Else we succeed if the reset-seek succeeds.
+ return seekres;
+}
+
+nsresult FileMediaResource::UnsafeRead(char* aBuffer, uint32_t aCount, uint32_t* aBytes)
+{
+ EnsureSizeInitialized();
+ return mInput->Read(aBuffer, aCount, aBytes);
+}
+
+nsresult FileMediaResource::ReadAt(int64_t aOffset, char* aBuffer,
+ uint32_t aCount, uint32_t* aBytes)
+{
+ NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
+
+ nsresult rv;
+ {
+ MutexAutoLock lock(mLock);
+ rv = UnsafeSeek(nsISeekableStream::NS_SEEK_SET, aOffset);
+ if (NS_FAILED(rv)) return rv;
+ rv = UnsafeRead(aBuffer, aCount, aBytes);
+ }
+ if (NS_SUCCEEDED(rv)) {
+ DispatchBytesConsumed(*aBytes, aOffset);
+ }
+ return rv;
+}
+
+already_AddRefed<MediaByteBuffer>
+FileMediaResource::MediaReadAt(int64_t aOffset, uint32_t aCount)
+{
+ NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
+
+ MutexAutoLock lock(mLock);
+ return UnsafeMediaReadAt(aOffset, aCount);
+}
+
+already_AddRefed<MediaByteBuffer>
+FileMediaResource::UnsafeMediaReadAt(int64_t aOffset, uint32_t aCount)
+{
+ RefPtr<MediaByteBuffer> bytes = new MediaByteBuffer();
+ bool ok = bytes->SetLength(aCount, fallible);
+ NS_ENSURE_TRUE(ok, nullptr);
+ nsresult rv = UnsafeSeek(nsISeekableStream::NS_SEEK_SET, aOffset);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+ char* curr = reinterpret_cast<char*>(bytes->Elements());
+ const char* start = curr;
+ while (aCount > 0) {
+ uint32_t bytesRead;
+ rv = UnsafeRead(curr, aCount, &bytesRead);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+ if (!bytesRead) {
+ break;
+ }
+ aCount -= bytesRead;
+ curr += bytesRead;
+ }
+ bytes->SetLength(curr - start);
+ return bytes.forget();
+}
+
+nsresult FileMediaResource::UnsafeSeek(int32_t aWhence, int64_t aOffset)
+{
+ NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
+
+ if (!mSeekable)
+ return NS_ERROR_FAILURE;
+ EnsureSizeInitialized();
+ return mSeekable->Seek(aWhence, aOffset);
+}
+
+int64_t FileMediaResource::Tell()
+{
+ MutexAutoLock lock(mLock);
+ EnsureSizeInitialized();
+
+ int64_t offset = 0;
+ // Return mSize as offset (end of stream) in case of error
+ if (!mSeekable || NS_FAILED(mSeekable->Tell(&offset)))
+ return mSize;
+ return offset;
+}
+
+already_AddRefed<MediaResource>
+MediaResource::Create(MediaResourceCallback* aCallback, nsIChannel* aChannel)
+{
+ NS_ASSERTION(NS_IsMainThread(),
+ "MediaResource::Open called on non-main thread");
+
+ // If the channel was redirected, we want the post-redirect URI;
+ // but if the URI scheme was expanded, say from chrome: to jar:file:,
+ // we want the original URI.
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ nsAutoCString contentType;
+ aChannel->GetContentType(contentType);
+
+ nsCOMPtr<nsIFileChannel> fc = do_QueryInterface(aChannel);
+ RefPtr<MediaResource> resource;
+ if (fc || IsBlobURI(uri)) {
+ resource = new FileMediaResource(aCallback, aChannel, uri, contentType);
+ } else {
+ resource = new ChannelMediaResource(aCallback, aChannel, uri, contentType);
+ }
+ return resource.forget();
+}
+
+void BaseMediaResource::SetLoadInBackground(bool aLoadInBackground) {
+ if (aLoadInBackground == mLoadInBackground) {
+ return;
+ }
+ mLoadInBackground = aLoadInBackground;
+ if (!mChannel) {
+ // No channel, resource is probably already loaded.
+ return;
+ }
+
+ MediaDecoderOwner* owner = mCallback->GetMediaOwner();
+ if (!owner) {
+ NS_WARNING("Null owner in MediaResource::SetLoadInBackground()");
+ return;
+ }
+ dom::HTMLMediaElement* element = owner->GetMediaElement();
+ if (!element) {
+ NS_WARNING("Null element in MediaResource::SetLoadInBackground()");
+ return;
+ }
+
+ bool isPending = false;
+ if (NS_SUCCEEDED(mChannel->IsPending(&isPending)) &&
+ isPending) {
+ nsLoadFlags loadFlags;
+ DebugOnly<nsresult> rv = mChannel->GetLoadFlags(&loadFlags);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "GetLoadFlags() failed!");
+
+ if (aLoadInBackground) {
+ loadFlags |= nsIRequest::LOAD_BACKGROUND;
+ } else {
+ loadFlags &= ~nsIRequest::LOAD_BACKGROUND;
+ }
+ ModifyLoadFlags(loadFlags);
+ }
+}
+
+void BaseMediaResource::ModifyLoadFlags(nsLoadFlags aFlags)
+{
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ nsresult rv = mChannel->GetLoadGroup(getter_AddRefs(loadGroup));
+ MOZ_ASSERT(NS_SUCCEEDED(rv), "GetLoadGroup() failed!");
+
+ nsresult status;
+ mChannel->GetStatus(&status);
+
+ bool inLoadGroup = false;
+ if (loadGroup) {
+ rv = loadGroup->RemoveRequest(mChannel, nullptr, status);
+ if (NS_SUCCEEDED(rv)) {
+ inLoadGroup = true;
+ }
+ }
+
+ rv = mChannel->SetLoadFlags(aFlags);
+ MOZ_ASSERT(NS_SUCCEEDED(rv), "SetLoadFlags() failed!");
+
+ if (inLoadGroup) {
+ rv = loadGroup->AddRequest(mChannel, nullptr);
+ MOZ_ASSERT(NS_SUCCEEDED(rv), "AddRequest() failed!");
+ }
+}
+
+void BaseMediaResource::DispatchBytesConsumed(int64_t aNumBytes, int64_t aOffset)
+{
+ if (aNumBytes <= 0) {
+ return;
+ }
+ mCallback->NotifyBytesConsumed(aNumBytes, aOffset);
+}
+
+nsresult
+MediaResourceIndex::Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes)
+{
+ NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
+
+ // We purposefuly don't check that we may attempt to read past
+ // mResource->GetLength() as the resource's length may change over time.
+
+ nsresult rv = ReadAt(mOffset, aBuffer, aCount, aBytes);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mOffset += *aBytes;
+ return NS_OK;
+}
+
+nsresult
+MediaResourceIndex::ReadAt(int64_t aOffset, char* aBuffer,
+ uint32_t aCount, uint32_t* aBytes) const
+{
+ *aBytes = 0;
+ while (aCount > 0) {
+ uint32_t bytesRead = 0;
+ nsresult rv = mResource->ReadAt(aOffset, aBuffer, aCount, &bytesRead);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!bytesRead) {
+ break;
+ }
+ *aBytes += bytesRead;
+ aOffset += bytesRead;
+ aBuffer += bytesRead;
+ aCount -= bytesRead;
+ }
+ return NS_OK;
+}
+
+nsresult
+MediaResourceIndex::Seek(int32_t aWhence, int64_t aOffset)
+{
+ switch (aWhence) {
+ case SEEK_SET:
+ break;
+ case SEEK_CUR:
+ aOffset += mOffset;
+ break;
+ case SEEK_END:
+ {
+ int64_t length = mResource->GetLength();
+ if (length == -1 || length - aOffset < 0) {
+ return NS_ERROR_FAILURE;
+ }
+ aOffset = mResource->GetLength() - aOffset;
+ }
+ break;
+ default:
+ return NS_ERROR_FAILURE;
+ }
+
+ mOffset = aOffset;
+
+ return NS_OK;
+}
+
+} // namespace mozilla
+