content/media/MediaResource.cpp

Fri, 16 Jan 2015 04:50:19 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Fri, 16 Jan 2015 04:50:19 +0100
branch
TOR_BUG_9701
changeset 13
44a2da4a2ab2
permissions
-rw-r--r--

Replace accessor implementation with direct member state manipulation, by
request https://trac.torproject.org/projects/tor/ticket/9701#comment:32

     1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
     3 /* This Source Code Form is subject to the terms of the Mozilla Public
     4  * License, v. 2.0. If a copy of the MPL was not distributed with this
     5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     7 #include "mozilla/DebugOnly.h"
     9 #include "MediaResource.h"
    10 #include "RtspMediaResource.h"
    12 #include "mozilla/Mutex.h"
    13 #include "nsDebug.h"
    14 #include "MediaDecoder.h"
    15 #include "nsNetUtil.h"
    16 #include "nsThreadUtils.h"
    17 #include "nsIFile.h"
    18 #include "nsIFileChannel.h"
    19 #include "nsIHttpChannel.h"
    20 #include "nsISeekableStream.h"
    21 #include "nsIInputStream.h"
    22 #include "nsIRequestObserver.h"
    23 #include "nsIStreamListener.h"
    24 #include "nsIScriptSecurityManager.h"
    25 #include "nsCrossSiteListenerProxy.h"
    26 #include "mozilla/dom/HTMLMediaElement.h"
    27 #include "nsError.h"
    28 #include "nsICachingChannel.h"
    29 #include "nsIAsyncVerifyRedirectCallback.h"
    30 #include "nsContentUtils.h"
    31 #include "nsHostObjectProtocolHandler.h"
    32 #include <algorithm>
    33 #include "nsProxyRelease.h"
    35 #ifdef PR_LOGGING
    36 PRLogModuleInfo* gMediaResourceLog;
    37 #define RESOURCE_LOG(msg, ...) PR_LOG(gMediaResourceLog, PR_LOG_DEBUG, \
    38                                       (msg, ##__VA_ARGS__))
    39 // Debug logging macro with object pointer and class name.
    40 #define CMLOG(msg, ...) \
    41         RESOURCE_LOG("%p [ChannelMediaResource]: " msg, this, ##__VA_ARGS__)
    42 #else
    43 #define RESOURCE_LOG(msg, ...)
    44 #define CMLOG(msg, ...)
    45 #endif
    47 static const uint32_t HTTP_OK_CODE = 200;
    48 static const uint32_t HTTP_PARTIAL_RESPONSE_CODE = 206;
    50 namespace mozilla {
    52 void
    53 MediaResource::Destroy()
    54 {
    55   // If we're being destroyed on a non-main thread, we AddRef again and
    56   // use a proxy to release the MediaResource on the main thread, where
    57   // the MediaResource is deleted. This ensures we only delete the
    58   // MediaResource on the main thread.
    59   if (!NS_IsMainThread()) {
    60     nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
    61     NS_ENSURE_TRUE_VOID(mainThread);
    62     nsRefPtr<MediaResource> doomed(this);
    63     if (NS_FAILED(NS_ProxyRelease(mainThread, doomed, true))) {
    64       NS_WARNING("Failed to proxy release to main thread!");
    65     }
    66   } else {
    67     delete this;
    68   }
    69 }
    71 NS_IMPL_ADDREF(MediaResource)
    72 NS_IMPL_RELEASE_WITH_DESTROY(MediaResource, Destroy())
    73 NS_IMPL_QUERY_INTERFACE0(MediaResource)
    75 ChannelMediaResource::ChannelMediaResource(MediaDecoder* aDecoder,
    76                                            nsIChannel* aChannel,
    77                                            nsIURI* aURI,
    78                                            const nsACString& aContentType)
    79   : BaseMediaResource(aDecoder, aChannel, aURI, aContentType),
    80     mOffset(0), mSuspendCount(0),
    81     mReopenOnError(false), mIgnoreClose(false),
    82     mCacheStream(MOZ_THIS_IN_INITIALIZER_LIST()),
    83     mLock("ChannelMediaResource.mLock"),
    84     mIgnoreResume(false),
    85     mSeekingForMetadata(false),
    86     mIsTransportSeekable(true)
    87 {
    88 #ifdef PR_LOGGING
    89   if (!gMediaResourceLog) {
    90     gMediaResourceLog = PR_NewLogModule("MediaResource");
    91   }
    92 #endif
    93 }
    95 ChannelMediaResource::~ChannelMediaResource()
    96 {
    97   if (mListener) {
    98     // Kill its reference to us since we're going away
    99     mListener->Revoke();
   100   }
   101 }
   103 // ChannelMediaResource::Listener just observes the channel and
   104 // forwards notifications to the ChannelMediaResource. We use multiple
   105 // listener objects so that when we open a new stream for a seek we can
   106 // disconnect the old listener from the ChannelMediaResource and hook up
   107 // a new listener, so notifications from the old channel are discarded
   108 // and don't confuse us.
   109 NS_IMPL_ISUPPORTS(ChannelMediaResource::Listener,
   110                   nsIRequestObserver, nsIStreamListener, nsIChannelEventSink,
   111                   nsIInterfaceRequestor)
   113 nsresult
   114 ChannelMediaResource::Listener::OnStartRequest(nsIRequest* aRequest,
   115                                                nsISupports* aContext)
   116 {
   117   if (!mResource)
   118     return NS_OK;
   119   return mResource->OnStartRequest(aRequest);
   120 }
   122 nsresult
   123 ChannelMediaResource::Listener::OnStopRequest(nsIRequest* aRequest,
   124                                               nsISupports* aContext,
   125                                               nsresult aStatus)
   126 {
   127   if (!mResource)
   128     return NS_OK;
   129   return mResource->OnStopRequest(aRequest, aStatus);
   130 }
   132 nsresult
   133 ChannelMediaResource::Listener::OnDataAvailable(nsIRequest* aRequest,
   134                                                 nsISupports* aContext,
   135                                                 nsIInputStream* aStream,
   136                                                 uint64_t aOffset,
   137                                                 uint32_t aCount)
   138 {
   139   if (!mResource)
   140     return NS_OK;
   141   return mResource->OnDataAvailable(aRequest, aStream, aCount);
   142 }
   144 nsresult
   145 ChannelMediaResource::Listener::AsyncOnChannelRedirect(nsIChannel* aOldChannel,
   146                                                        nsIChannel* aNewChannel,
   147                                                        uint32_t aFlags,
   148                                                        nsIAsyncVerifyRedirectCallback* cb)
   149 {
   150   nsresult rv = NS_OK;
   151   if (mResource)
   152     rv = mResource->OnChannelRedirect(aOldChannel, aNewChannel, aFlags);
   154   if (NS_FAILED(rv))
   155     return rv;
   157   cb->OnRedirectVerifyCallback(NS_OK);
   158   return NS_OK;
   159 }
   161 nsresult
   162 ChannelMediaResource::Listener::GetInterface(const nsIID & aIID, void **aResult)
   163 {
   164   return QueryInterface(aIID, aResult);
   165 }
   167 nsresult
   168 ChannelMediaResource::OnStartRequest(nsIRequest* aRequest)
   169 {
   170   NS_ASSERTION(mChannel.get() == aRequest, "Wrong channel!");
   172   MediaDecoderOwner* owner = mDecoder->GetMediaOwner();
   173   NS_ENSURE_TRUE(owner, NS_ERROR_FAILURE);
   174   dom::HTMLMediaElement* element = owner->GetMediaElement();
   175   NS_ENSURE_TRUE(element, NS_ERROR_FAILURE);
   176   nsresult status;
   177   nsresult rv = aRequest->GetStatus(&status);
   178   NS_ENSURE_SUCCESS(rv, rv);
   180   if (status == NS_BINDING_ABORTED) {
   181     // Request was aborted before we had a chance to receive any data, or
   182     // even an OnStartRequest(). Close the channel. This is important, as
   183     // we don't want to mess up our state, as if we're cloned that would
   184     // cause the clone to copy incorrect metadata (like whether we're
   185     // infinite for example).
   186     CloseChannel();
   187     return status;
   188   }
   190   if (element->ShouldCheckAllowOrigin()) {
   191     // If the request was cancelled by nsCORSListenerProxy due to failing
   192     // the CORS security check, send an error through to the media element.
   193     if (status == NS_ERROR_DOM_BAD_URI) {
   194       mDecoder->NetworkError();
   195       return NS_ERROR_DOM_BAD_URI;
   196     }
   197   }
   199   nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(aRequest);
   200   bool seekable = false;
   201   if (hc) {
   202     uint32_t responseStatus = 0;
   203     hc->GetResponseStatus(&responseStatus);
   204     bool succeeded = false;
   205     hc->GetRequestSucceeded(&succeeded);
   207     if (!succeeded && NS_SUCCEEDED(status)) {
   208       // HTTP-level error (e.g. 4xx); treat this as a fatal network-level error.
   209       // We might get this on a seek.
   210       // (Note that lower-level errors indicated by NS_FAILED(status) are
   211       // handled in OnStopRequest.)
   212       // A 416 error should treated as EOF here... it's possible
   213       // that we don't get Content-Length, we read N bytes, then we
   214       // suspend and resume, the resume reopens the channel and we seek to
   215       // offset N, but there are no more bytes, so we get a 416
   216       // "Requested Range Not Satisfiable".
   217       if (responseStatus == HTTP_REQUESTED_RANGE_NOT_SATISFIABLE_CODE) {
   218         // OnStopRequest will not be fired, so we need to do some of its
   219         // work here.
   220         mCacheStream.NotifyDataEnded(status);
   221       } else {
   222         mDecoder->NetworkError();
   223       }
   225       // This disconnects our listener so we don't get any more data. We
   226       // certainly don't want an error page to end up in our cache!
   227       CloseChannel();
   228       return NS_OK;
   229     }
   231     nsAutoCString ranges;
   232     hc->GetResponseHeader(NS_LITERAL_CSTRING("Accept-Ranges"),
   233                           ranges);
   234     bool acceptsRanges = ranges.EqualsLiteral("bytes");
   235     // True if this channel will not return an unbounded amount of data
   236     bool dataIsBounded = false;
   238     int64_t contentLength = -1;
   239     hc->GetContentLength(&contentLength);
   240     if (contentLength >= 0 && responseStatus == HTTP_OK_CODE) {
   241       // "OK" status means Content-Length is for the whole resource.
   242       // Since that's bounded, we know we have a finite-length resource.
   243       dataIsBounded = true;
   244     }
   246     if (mOffset == 0) {
   247       // Look for duration headers from known Ogg content systems.
   248       // In the case of multiple options for obtaining the duration
   249       // the order of precedence is:
   250       // 1) The Media resource metadata if possible (done by the decoder itself).
   251       // 2) Content-Duration message header.
   252       // 3) X-AMZ-Meta-Content-Duration.
   253       // 4) X-Content-Duration.
   254       // 5) Perform a seek in the decoder to find the value.
   255       nsAutoCString durationText;
   256       nsresult ec = NS_OK;
   257       rv = hc->GetResponseHeader(NS_LITERAL_CSTRING("Content-Duration"), durationText);
   258       if (NS_FAILED(rv)) {
   259         rv = hc->GetResponseHeader(NS_LITERAL_CSTRING("X-AMZ-Meta-Content-Duration"), durationText);
   260       }
   261       if (NS_FAILED(rv)) {
   262         rv = hc->GetResponseHeader(NS_LITERAL_CSTRING("X-Content-Duration"), durationText);
   263       }
   265       // If there is a Content-Duration header with a valid value, record
   266       // the duration.
   267       if (NS_SUCCEEDED(rv)) {
   268         double duration = durationText.ToDouble(&ec);
   269         if (ec == NS_OK && duration >= 0) {
   270           mDecoder->SetDuration(duration);
   271           // We know the resource must be bounded.
   272           dataIsBounded = true;
   273         }
   274       }
   275     }
   277     // Assume Range requests have a bounded upper limit unless the
   278     // Content-Range header tells us otherwise.
   279     bool boundedSeekLimit = true;
   280     // Check response code for byte-range requests (seeking, chunk requests).
   281     if (!mByteRange.IsNull() && (responseStatus == HTTP_PARTIAL_RESPONSE_CODE)) {
   282       // Parse Content-Range header.
   283       int64_t rangeStart = 0;
   284       int64_t rangeEnd = 0;
   285       int64_t rangeTotal = 0;
   286       rv = ParseContentRangeHeader(hc, rangeStart, rangeEnd, rangeTotal);
   287       if (NS_FAILED(rv)) {
   288         // Content-Range header text should be parse-able.
   289         CMLOG("Error processing \'Content-Range' for "
   290               "HTTP_PARTIAL_RESPONSE_CODE: rv[%x] channel[%p] decoder[%p]",
   291               rv, hc.get(), mDecoder);
   292         mDecoder->NetworkError();
   293         CloseChannel();
   294         return NS_OK;
   295       }
   297       // Give some warnings if the ranges are unexpected.
   298       // XXX These could be error conditions.
   299       NS_WARN_IF_FALSE(mByteRange.mStart == rangeStart,
   300                        "response range start does not match request");
   301       NS_WARN_IF_FALSE(mOffset == rangeStart,
   302                        "response range start does not match current offset");
   303       NS_WARN_IF_FALSE(mByteRange.mEnd == rangeEnd,
   304                        "response range end does not match request");
   305       // Notify media cache about the length and start offset of data received.
   306       // Note: If aRangeTotal == -1, then the total bytes is unknown at this stage.
   307       //       For now, tell the decoder that the stream is infinite.
   308       if (rangeTotal == -1) {
   309         boundedSeekLimit = false;
   310       } else {
   311         mCacheStream.NotifyDataLength(rangeTotal);
   312       }
   313       mCacheStream.NotifyDataStarted(rangeStart);
   315       mOffset = rangeStart;
   316       // We received 'Content-Range', so the server accepts range requests.
   317       acceptsRanges = true;
   318     } else if (((mOffset > 0) || !mByteRange.IsNull())
   319                && (responseStatus == HTTP_OK_CODE)) {
   320       // If we get an OK response but we were seeking, or requesting a byte
   321       // range, then we have to assume that seeking doesn't work. We also need
   322       // to tell the cache that it's getting data for the start of the stream.
   323       mCacheStream.NotifyDataStarted(0);
   324       mOffset = 0;
   326       // The server claimed it supported range requests.  It lied.
   327       acceptsRanges = false;
   328     } else if (mOffset == 0 &&
   329                (responseStatus == HTTP_OK_CODE ||
   330                 responseStatus == HTTP_PARTIAL_RESPONSE_CODE)) {
   331       if (contentLength >= 0) {
   332         mCacheStream.NotifyDataLength(contentLength);
   333       }
   334     }
   335     // XXX we probably should examine the Content-Range header in case
   336     // the server gave us a range which is not quite what we asked for
   338     // If we get an HTTP_OK_CODE response to our byte range request,
   339     // and the server isn't sending Accept-Ranges:bytes then we don't
   340     // support seeking.
   341     seekable =
   342       responseStatus == HTTP_PARTIAL_RESPONSE_CODE || acceptsRanges;
   343     if (seekable && boundedSeekLimit) {
   344       // If range requests are supported, and we did not see an unbounded
   345       // upper range limit, we assume the resource is bounded.
   346       dataIsBounded = true;
   347     }
   349     mDecoder->SetInfinite(!dataIsBounded);
   350   }
   351   mDecoder->SetTransportSeekable(seekable);
   352   mCacheStream.SetTransportSeekable(seekable);
   354   {
   355     MutexAutoLock lock(mLock);
   356     mIsTransportSeekable = seekable;
   357     mChannelStatistics->Start();
   358   }
   360   mReopenOnError = false;
   361   // If we are seeking to get metadata, because we are playing an OGG file,
   362   // ignore if the channel gets closed without us suspending it explicitly. We
   363   // don't want to tell the element that the download has finished whereas we
   364   // just happended to have reached the end of the media while seeking.
   365   mIgnoreClose = mSeekingForMetadata;
   367   if (mSuspendCount > 0) {
   368     // Re-suspend the channel if it needs to be suspended
   369     // No need to call PossiblySuspend here since the channel is
   370     // definitely in the right state for us in OnStartRequest.
   371     mChannel->Suspend();
   372     mIgnoreResume = false;
   373   }
   375   // Fires an initial progress event and sets up the stall counter so stall events
   376   // fire if no download occurs within the required time frame.
   377   mDecoder->Progress(false);
   379   return NS_OK;
   380 }
   382 bool
   383 ChannelMediaResource::IsTransportSeekable()
   384 {
   385   MutexAutoLock lock(mLock);
   386   return mIsTransportSeekable;
   387 }
   389 nsresult
   390 ChannelMediaResource::ParseContentRangeHeader(nsIHttpChannel * aHttpChan,
   391                                               int64_t& aRangeStart,
   392                                               int64_t& aRangeEnd,
   393                                               int64_t& aRangeTotal)
   394 {
   395   NS_ENSURE_ARG(aHttpChan);
   397   nsAutoCString rangeStr;
   398   nsresult rv = aHttpChan->GetResponseHeader(NS_LITERAL_CSTRING("Content-Range"),
   399                                              rangeStr);
   400   NS_ENSURE_SUCCESS(rv, rv);
   401   NS_ENSURE_FALSE(rangeStr.IsEmpty(), NS_ERROR_ILLEGAL_VALUE);
   403   // Parse the range header: e.g. Content-Range: bytes 7000-7999/8000.
   404   int32_t spacePos = rangeStr.Find(NS_LITERAL_CSTRING(" "));
   405   int32_t dashPos = rangeStr.Find(NS_LITERAL_CSTRING("-"), true, spacePos);
   406   int32_t slashPos = rangeStr.Find(NS_LITERAL_CSTRING("/"), true, dashPos);
   408   nsAutoCString aRangeStartText;
   409   rangeStr.Mid(aRangeStartText, spacePos+1, dashPos-(spacePos+1));
   410   aRangeStart = aRangeStartText.ToInteger64(&rv);
   411   NS_ENSURE_SUCCESS(rv, rv);
   412   NS_ENSURE_TRUE(0 <= aRangeStart, NS_ERROR_ILLEGAL_VALUE);
   414   nsAutoCString aRangeEndText;
   415   rangeStr.Mid(aRangeEndText, dashPos+1, slashPos-(dashPos+1));
   416   aRangeEnd = aRangeEndText.ToInteger64(&rv);
   417   NS_ENSURE_SUCCESS(rv, rv);
   418   NS_ENSURE_TRUE(aRangeStart < aRangeEnd, NS_ERROR_ILLEGAL_VALUE);
   420   nsAutoCString aRangeTotalText;
   421   rangeStr.Right(aRangeTotalText, rangeStr.Length()-(slashPos+1));
   422   if (aRangeTotalText[0] == '*') {
   423     aRangeTotal = -1;
   424   } else {
   425     aRangeTotal = aRangeTotalText.ToInteger64(&rv);
   426     NS_ENSURE_TRUE(aRangeEnd < aRangeTotal, NS_ERROR_ILLEGAL_VALUE);
   427     NS_ENSURE_SUCCESS(rv, rv);
   428   }
   430   CMLOG("Received bytes [%lld] to [%lld] of [%lld] for decoder[%p]",
   431         aRangeStart, aRangeEnd, aRangeTotal, mDecoder);
   433   return NS_OK;
   434 }
   436 nsresult
   437 ChannelMediaResource::OnStopRequest(nsIRequest* aRequest, nsresult aStatus)
   438 {
   439   NS_ASSERTION(mChannel.get() == aRequest, "Wrong channel!");
   440   NS_ASSERTION(mSuspendCount == 0,
   441                "How can OnStopRequest fire while we're suspended?");
   443   {
   444     MutexAutoLock lock(mLock);
   445     mChannelStatistics->Stop();
   446   }
   448   // Note that aStatus might have succeeded --- this might be a normal close
   449   // --- even in situations where the server cut us off because we were
   450   // suspended. So we need to "reopen on error" in that case too. The only
   451   // cases where we don't need to reopen are when *we* closed the stream.
   452   // But don't reopen if we need to seek and we don't think we can... that would
   453   // cause us to just re-read the stream, which would be really bad.
   454   if (mReopenOnError &&
   455       aStatus != NS_ERROR_PARSED_DATA_CACHED && aStatus != NS_BINDING_ABORTED &&
   456       (mOffset == 0 || mCacheStream.IsTransportSeekable())) {
   457     // If the stream did close normally, then if the server is seekable we'll
   458     // just seek to the end of the resource and get an HTTP 416 error because
   459     // there's nothing there, so this isn't bad.
   460     nsresult rv = CacheClientSeek(mOffset, false);
   461     if (NS_SUCCEEDED(rv))
   462       return rv;
   463     // If the reopen/reseek fails, just fall through and treat this
   464     // error as fatal.
   465   }
   467   if (!mIgnoreClose) {
   468     mCacheStream.NotifyDataEnded(aStatus);
   470     // Move this request back into the foreground.  This is necessary for
   471     // requests owned by video documents to ensure the load group fires
   472     // OnStopRequest when restoring from session history.
   473     nsLoadFlags loadFlags;
   474     DebugOnly<nsresult> rv = mChannel->GetLoadFlags(&loadFlags);
   475     NS_ASSERTION(NS_SUCCEEDED(rv), "GetLoadFlags() failed!");
   477     if (loadFlags & nsIRequest::LOAD_BACKGROUND) {
   478       ModifyLoadFlags(loadFlags & ~nsIRequest::LOAD_BACKGROUND);
   479     }
   480   }
   482   return NS_OK;
   483 }
   485 nsresult
   486 ChannelMediaResource::OnChannelRedirect(nsIChannel* aOld, nsIChannel* aNew,
   487                                         uint32_t aFlags)
   488 {
   489   mChannel = aNew;
   490   SetupChannelHeaders();
   491   return NS_OK;
   492 }
   494 struct CopySegmentClosure {
   495   nsCOMPtr<nsIPrincipal> mPrincipal;
   496   ChannelMediaResource*  mResource;
   497 };
   499 NS_METHOD
   500 ChannelMediaResource::CopySegmentToCache(nsIInputStream *aInStream,
   501                                          void *aClosure,
   502                                          const char *aFromSegment,
   503                                          uint32_t aToOffset,
   504                                          uint32_t aCount,
   505                                          uint32_t *aWriteCount)
   506 {
   507   CopySegmentClosure* closure = static_cast<CopySegmentClosure*>(aClosure);
   509   closure->mResource->mDecoder->NotifyDataArrived(aFromSegment, aCount, closure->mResource->mOffset);
   511   // Keep track of where we're up to.
   512   RESOURCE_LOG("%p [ChannelMediaResource]: CopySegmentToCache at mOffset [%lld] add "
   513                "[%d] bytes for decoder[%p]",
   514                closure->mResource, closure->mResource->mOffset, aCount,
   515                closure->mResource->mDecoder);
   516   closure->mResource->mOffset += aCount;
   518   closure->mResource->mCacheStream.NotifyDataReceived(aCount, aFromSegment,
   519                                                       closure->mPrincipal);
   520   *aWriteCount = aCount;
   521   return NS_OK;
   522 }
   524 nsresult
   525 ChannelMediaResource::OnDataAvailable(nsIRequest* aRequest,
   526                                       nsIInputStream* aStream,
   527                                       uint32_t aCount)
   528 {
   529   NS_ASSERTION(mChannel.get() == aRequest, "Wrong channel!");
   531   {
   532     MutexAutoLock lock(mLock);
   533     mChannelStatistics->AddBytes(aCount);
   534   }
   536   CopySegmentClosure closure;
   537   nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
   538   if (secMan && mChannel) {
   539     secMan->GetChannelPrincipal(mChannel, getter_AddRefs(closure.mPrincipal));
   540   }
   541   closure.mResource = this;
   543   uint32_t count = aCount;
   544   while (count > 0) {
   545     uint32_t read;
   546     nsresult rv = aStream->ReadSegments(CopySegmentToCache, &closure, count,
   547                                         &read);
   548     if (NS_FAILED(rv))
   549       return rv;
   550     NS_ASSERTION(read > 0, "Read 0 bytes while data was available?");
   551     count -= read;
   552   }
   554   return NS_OK;
   555 }
   557 nsresult ChannelMediaResource::Open(nsIStreamListener **aStreamListener)
   558 {
   559   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
   561   if (!mChannelStatistics) {
   562     mChannelStatistics = new MediaChannelStatistics();
   563   }
   565   nsresult rv = mCacheStream.Init();
   566   if (NS_FAILED(rv))
   567     return rv;
   568   NS_ASSERTION(mOffset == 0, "Who set mOffset already?");
   570   if (!mChannel) {
   571     // When we're a clone, the decoder might ask us to Open even though
   572     // we haven't established an mChannel (because we might not need one)
   573     NS_ASSERTION(!aStreamListener,
   574                  "Should have already been given a channel if we're to return a stream listener");
   575     return NS_OK;
   576   }
   578   return OpenChannel(aStreamListener);
   579 }
   581 nsresult ChannelMediaResource::OpenChannel(nsIStreamListener** aStreamListener)
   582 {
   583   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
   584   NS_ENSURE_TRUE(mChannel, NS_ERROR_NULL_POINTER);
   585   NS_ASSERTION(!mListener, "Listener should have been removed by now");
   587   if (aStreamListener) {
   588     *aStreamListener = nullptr;
   589   }
   591   if (mByteRange.IsNull()) {
   592     // We're not making a byte range request, so set the content length,
   593     // if it's available as an HTTP header. This ensures that MediaResource
   594     // wrapping objects for platform libraries that expect to know
   595     // the length of a resource can get it before OnStartRequest() fires.
   596     nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(mChannel);
   597     if (hc) {
   598       int64_t cl = -1;
   599       if (NS_SUCCEEDED(hc->GetContentLength(&cl)) && cl != -1) {
   600         mCacheStream.NotifyDataLength(cl);
   601       }
   602     }
   603   }
   605   mListener = new Listener(this);
   606   NS_ENSURE_TRUE(mListener, NS_ERROR_OUT_OF_MEMORY);
   608   if (aStreamListener) {
   609     *aStreamListener = mListener;
   610     NS_ADDREF(*aStreamListener);
   611   } else {
   612     mChannel->SetNotificationCallbacks(mListener.get());
   614     nsCOMPtr<nsIStreamListener> listener = mListener.get();
   616     // Ensure that if we're loading cross domain, that the server is sending
   617     // an authorizing Access-Control header.
   618     MediaDecoderOwner* owner = mDecoder->GetMediaOwner();
   619     NS_ENSURE_TRUE(owner, NS_ERROR_FAILURE);
   620     dom::HTMLMediaElement* element = owner->GetMediaElement();
   621     NS_ENSURE_TRUE(element, NS_ERROR_FAILURE);
   622     if (element->ShouldCheckAllowOrigin()) {
   623       nsRefPtr<nsCORSListenerProxy> crossSiteListener =
   624         new nsCORSListenerProxy(mListener,
   625                                 element->NodePrincipal(),
   626                                 false);
   627       nsresult rv = crossSiteListener->Init(mChannel);
   628       listener = crossSiteListener;
   629       NS_ENSURE_TRUE(crossSiteListener, NS_ERROR_OUT_OF_MEMORY);
   630       NS_ENSURE_SUCCESS(rv, rv);
   631     } else {
   632       nsresult rv = nsContentUtils::GetSecurityManager()->
   633         CheckLoadURIWithPrincipal(element->NodePrincipal(),
   634                                   mURI,
   635                                   nsIScriptSecurityManager::STANDARD);
   636       NS_ENSURE_SUCCESS(rv, rv);
   637     }
   639     SetupChannelHeaders();
   641     nsresult rv = mChannel->AsyncOpen(listener, nullptr);
   642     NS_ENSURE_SUCCESS(rv, rv);
   643     // Tell the media element that we are fetching data from a channel.
   644     element->DownloadResumed(true);
   645   }
   647   return NS_OK;
   648 }
   650 void ChannelMediaResource::SetupChannelHeaders()
   651 {
   652   // Always use a byte range request even if we're reading from the start
   653   // of the resource.
   654   // This enables us to detect if the stream supports byte range
   655   // requests, and therefore seeking, early.
   656   nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(mChannel);
   657   if (hc) {
   658     // Use |mByteRange| for a specific chunk, or |mOffset| if seeking in a
   659     // complete file download.
   660     nsAutoCString rangeString("bytes=");
   661     if (!mByteRange.IsNull()) {
   662       rangeString.AppendInt(mByteRange.mStart);
   663       mOffset = mByteRange.mStart;
   664     } else {
   665       rangeString.AppendInt(mOffset);
   666     }
   667     rangeString.Append("-");
   668     if (!mByteRange.IsNull()) {
   669       rangeString.AppendInt(mByteRange.mEnd);
   670     }
   671     hc->SetRequestHeader(NS_LITERAL_CSTRING("Range"), rangeString, false);
   673     // Send Accept header for video and audio types only (Bug 489071)
   674     NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread");
   675     MediaDecoderOwner* owner = mDecoder->GetMediaOwner();
   676     if (!owner) {
   677       return;
   678     }
   679     dom::HTMLMediaElement* element = owner->GetMediaElement();
   680     if (!element) {
   681       return;
   682     }
   683     element->SetRequestHeaders(hc);
   684   } else {
   685     NS_ASSERTION(mOffset == 0, "Don't know how to seek on this channel type");
   686   }
   687 }
   689 nsresult ChannelMediaResource::Close()
   690 {
   691   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
   693   mCacheStream.Close();
   694   CloseChannel();
   695   return NS_OK;
   696 }
   698 already_AddRefed<nsIPrincipal> ChannelMediaResource::GetCurrentPrincipal()
   699 {
   700   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
   702   nsCOMPtr<nsIPrincipal> principal = mCacheStream.GetCurrentPrincipal();
   703   return principal.forget();
   704 }
   706 bool ChannelMediaResource::CanClone()
   707 {
   708   return mCacheStream.IsAvailableForSharing();
   709 }
   711 already_AddRefed<MediaResource> ChannelMediaResource::CloneData(MediaDecoder* aDecoder)
   712 {
   713   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
   714   NS_ASSERTION(mCacheStream.IsAvailableForSharing(), "Stream can't be cloned");
   716   nsRefPtr<ChannelMediaResource> resource =
   717     new ChannelMediaResource(aDecoder,
   718                              nullptr,
   719                              mURI,
   720                              GetContentType());
   721   if (resource) {
   722     // Initially the clone is treated as suspended by the cache, because
   723     // we don't have a channel. If the cache needs to read data from the clone
   724     // it will call CacheClientResume (or CacheClientSeek with aResume true)
   725     // which will recreate the channel. This way, if all of the media data
   726     // is already in the cache we don't create an unnecessary HTTP channel
   727     // and perform a useless HTTP transaction.
   728     resource->mSuspendCount = 1;
   729     resource->mCacheStream.InitAsClone(&mCacheStream);
   730     resource->mChannelStatistics = new MediaChannelStatistics(mChannelStatistics);
   731     resource->mChannelStatistics->Stop();
   732   }
   733   return resource.forget();
   734 }
   736 void ChannelMediaResource::CloseChannel()
   737 {
   738   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
   740   {
   741     MutexAutoLock lock(mLock);
   742     mChannelStatistics->Stop();
   743   }
   745   if (mListener) {
   746     mListener->Revoke();
   747     mListener = nullptr;
   748   }
   750   if (mChannel) {
   751     if (mSuspendCount > 0) {
   752       // Resume the channel before we cancel it
   753       PossiblyResume();
   754     }
   755     // The status we use here won't be passed to the decoder, since
   756     // we've already revoked the listener. It can however be passed
   757     // to nsDocumentViewer::LoadComplete if our channel is the one
   758     // that kicked off creation of a video document. We don't want that
   759     // document load to think there was an error.
   760     // NS_ERROR_PARSED_DATA_CACHED is the best thing we have for that
   761     // at the moment.
   762     mChannel->Cancel(NS_ERROR_PARSED_DATA_CACHED);
   763     mChannel = nullptr;
   764   }
   765 }
   767 nsresult ChannelMediaResource::ReadFromCache(char* aBuffer,
   768                                              int64_t aOffset,
   769                                              uint32_t aCount)
   770 {
   771   return mCacheStream.ReadFromCache(aBuffer, aOffset, aCount);
   772 }
   774 nsresult ChannelMediaResource::Read(char* aBuffer,
   775                                     uint32_t aCount,
   776                                     uint32_t* aBytes)
   777 {
   778   NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
   780   int64_t offset = mCacheStream.Tell();
   781   nsresult rv = mCacheStream.Read(aBuffer, aCount, aBytes);
   782   if (NS_SUCCEEDED(rv)) {
   783     DispatchBytesConsumed(*aBytes, offset);
   784   }
   785   return rv;
   786 }
   788 nsresult ChannelMediaResource::ReadAt(int64_t aOffset,
   789                                       char* aBuffer,
   790                                       uint32_t aCount,
   791                                       uint32_t* aBytes)
   792 {
   793   NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
   795   nsresult rv = mCacheStream.ReadAt(aOffset, aBuffer, aCount, aBytes);
   796   if (NS_SUCCEEDED(rv)) {
   797     DispatchBytesConsumed(*aBytes, aOffset);
   798   }
   799   return rv;
   800 }
   802 nsresult ChannelMediaResource::Seek(int32_t aWhence, int64_t aOffset)
   803 {
   804   NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
   806   CMLOG("Seek requested for aOffset [%lld] for decoder [%p]",
   807         aOffset, mDecoder);
   808   return mCacheStream.Seek(aWhence, aOffset);
   809 }
   811 void ChannelMediaResource::StartSeekingForMetadata()
   812 {
   813   mSeekingForMetadata = true;
   814 }
   816 void ChannelMediaResource::EndSeekingForMetadata()
   817 {
   818   mSeekingForMetadata = false;
   819 }
   821 int64_t ChannelMediaResource::Tell()
   822 {
   823   NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
   825   return mCacheStream.Tell();
   826 }
   828 nsresult ChannelMediaResource::GetCachedRanges(nsTArray<MediaByteRange>& aRanges)
   829 {
   830   return mCacheStream.GetCachedRanges(aRanges);
   831 }
   833 void ChannelMediaResource::Suspend(bool aCloseImmediately)
   834 {
   835   NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread");
   837   MediaDecoderOwner* owner = mDecoder->GetMediaOwner();
   838   if (!owner) {
   839     // Shutting down; do nothing.
   840     return;
   841   }
   842   dom::HTMLMediaElement* element = owner->GetMediaElement();
   843   if (!element) {
   844     // Shutting down; do nothing.
   845     return;
   846   }
   848   if (mChannel) {
   849     if (aCloseImmediately && mCacheStream.IsTransportSeekable()) {
   850       // Kill off our channel right now, but don't tell anyone about it.
   851       mIgnoreClose = true;
   852       CloseChannel();
   853       element->DownloadSuspended();
   854     } else if (mSuspendCount == 0) {
   855       {
   856         MutexAutoLock lock(mLock);
   857         mChannelStatistics->Stop();
   858       }
   859       PossiblySuspend();
   860       element->DownloadSuspended();
   861     }
   862   }
   864   ++mSuspendCount;
   865 }
   867 void ChannelMediaResource::Resume()
   868 {
   869   NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread");
   870   NS_ASSERTION(mSuspendCount > 0, "Too many resumes!");
   872   MediaDecoderOwner* owner = mDecoder->GetMediaOwner();
   873   if (!owner) {
   874     // Shutting down; do nothing.
   875     return;
   876   }
   877   dom::HTMLMediaElement* element = owner->GetMediaElement();
   878   if (!element) {
   879     // Shutting down; do nothing.
   880     return;
   881   }
   883   NS_ASSERTION(mSuspendCount > 0, "Resume without previous Suspend!");
   884   --mSuspendCount;
   885   if (mSuspendCount == 0) {
   886     if (mChannel) {
   887       // Just wake up our existing channel
   888       {
   889         MutexAutoLock lock(mLock);
   890         mChannelStatistics->Start();
   891       }
   892       // if an error occurs after Resume, assume it's because the server
   893       // timed out the connection and we should reopen it.
   894       mReopenOnError = true;
   895       PossiblyResume();
   896       element->DownloadResumed();
   897     } else {
   898       int64_t totalLength = mCacheStream.GetLength();
   899       // If mOffset is at the end of the stream, then we shouldn't try to
   900       // seek to it. The seek will fail and be wasted anyway. We can leave
   901       // the channel dead; if the media cache wants to read some other data
   902       // in the future, it will call CacheClientSeek itself which will reopen the
   903       // channel.
   904       if (totalLength < 0 || mOffset < totalLength) {
   905         // There is (or may be) data to read at mOffset, so start reading it.
   906         // Need to recreate the channel.
   907         CacheClientSeek(mOffset, false);
   908       }
   909       element->DownloadResumed();
   910     }
   911   }
   912 }
   914 nsresult
   915 ChannelMediaResource::RecreateChannel()
   916 {
   917   nsLoadFlags loadFlags =
   918     nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE_IF_BUSY |
   919     (mLoadInBackground ? nsIRequest::LOAD_BACKGROUND : 0);
   921   MediaDecoderOwner* owner = mDecoder->GetMediaOwner();
   922   if (!owner) {
   923     // The decoder is being shut down, so don't bother opening a new channel
   924     return NS_OK;
   925   }
   926   dom::HTMLMediaElement* element = owner->GetMediaElement();
   927   if (!element) {
   928     // The decoder is being shut down, so don't bother opening a new channel
   929     return NS_OK;
   930   }
   931   nsCOMPtr<nsILoadGroup> loadGroup = element->GetDocumentLoadGroup();
   932   NS_ENSURE_TRUE(loadGroup, NS_ERROR_NULL_POINTER);
   934   nsresult rv = NS_NewChannel(getter_AddRefs(mChannel),
   935                               mURI,
   936                               nullptr,
   937                               loadGroup,
   938                               nullptr,
   939                               loadFlags);
   941   // We have cached the Content-Type, which should not change. Give a hint to
   942   // the channel to avoid a sniffing failure, which would be expected because we
   943   // are probably seeking in the middle of the bitstream, and sniffing relies
   944   // on the presence of a magic number at the beginning of the stream.
   945   NS_ASSERTION(!GetContentType().IsEmpty(),
   946       "When recreating a channel, we should know the Content-Type.");
   947   mChannel->SetContentType(GetContentType());
   949   return rv;
   950 }
   952 void
   953 ChannelMediaResource::DoNotifyDataReceived()
   954 {
   955   mDataReceivedEvent.Revoke();
   956   mDecoder->NotifyBytesDownloaded();
   957 }
   959 void
   960 ChannelMediaResource::CacheClientNotifyDataReceived()
   961 {
   962   NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread");
   963   // NOTE: this can be called with the media cache lock held, so don't
   964   // block or do anything which might try to acquire a lock!
   966   if (mDataReceivedEvent.IsPending())
   967     return;
   969   mDataReceivedEvent =
   970     NS_NewNonOwningRunnableMethod(this, &ChannelMediaResource::DoNotifyDataReceived);
   971   NS_DispatchToMainThread(mDataReceivedEvent.get(), NS_DISPATCH_NORMAL);
   972 }
   974 class DataEnded : public nsRunnable {
   975 public:
   976   DataEnded(MediaDecoder* aDecoder, nsresult aStatus) :
   977     mDecoder(aDecoder), mStatus(aStatus) {}
   978   NS_IMETHOD Run() {
   979     mDecoder->NotifyDownloadEnded(mStatus);
   980     return NS_OK;
   981   }
   982 private:
   983   nsRefPtr<MediaDecoder> mDecoder;
   984   nsresult                 mStatus;
   985 };
   987 void
   988 ChannelMediaResource::CacheClientNotifyDataEnded(nsresult aStatus)
   989 {
   990   NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread");
   991   // NOTE: this can be called with the media cache lock held, so don't
   992   // block or do anything which might try to acquire a lock!
   994   nsCOMPtr<nsIRunnable> event = new DataEnded(mDecoder, aStatus);
   995   NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
   996 }
   998 void
   999 ChannelMediaResource::CacheClientNotifyPrincipalChanged()
  1001   NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread");
  1003   mDecoder->NotifyPrincipalChanged();
  1006 nsresult
  1007 ChannelMediaResource::CacheClientSeek(int64_t aOffset, bool aResume)
  1009   NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread");
  1011   CMLOG("CacheClientSeek requested for aOffset [%lld] for decoder [%p]",
  1012         aOffset, mDecoder);
  1014   CloseChannel();
  1016   if (aResume) {
  1017     NS_ASSERTION(mSuspendCount > 0, "Too many resumes!");
  1018     // No need to mess with the channel, since we're making a new one
  1019     --mSuspendCount;
  1022   mOffset = aOffset;
  1024   if (mSuspendCount > 0) {
  1025     // Close the existing channel to force the channel to be recreated at
  1026     // the correct offset upon resume.
  1027     if (mChannel) {
  1028       mIgnoreClose = true;
  1029       CloseChannel();
  1031     return NS_OK;
  1034   nsresult rv = RecreateChannel();
  1035   if (NS_FAILED(rv))
  1036     return rv;
  1038   return OpenChannel(nullptr);
  1041 void
  1042 ChannelMediaResource::FlushCache()
  1044   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
  1046   // Ensure that data in the cache's partial block is written to disk.
  1047   mCacheStream.FlushPartialBlock();
  1050 void
  1051 ChannelMediaResource::NotifyLastByteRange()
  1053   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
  1055   // Tell media cache that the last data has been downloaded.
  1056   // Note: subsequent seeks will require re-opening the channel etc.
  1057   mCacheStream.NotifyDataEnded(NS_OK);
  1061 nsresult
  1062 ChannelMediaResource::CacheClientSuspend()
  1064   Suspend(false);
  1066   mDecoder->NotifySuspendedStatusChanged();
  1067   return NS_OK;
  1070 nsresult
  1071 ChannelMediaResource::CacheClientResume()
  1073   Resume();
  1075   mDecoder->NotifySuspendedStatusChanged();
  1076   return NS_OK;
  1079 int64_t
  1080 ChannelMediaResource::GetNextCachedData(int64_t aOffset)
  1082   return mCacheStream.GetNextCachedData(aOffset);
  1085 int64_t
  1086 ChannelMediaResource::GetCachedDataEnd(int64_t aOffset)
  1088   return mCacheStream.GetCachedDataEnd(aOffset);
  1091 bool
  1092 ChannelMediaResource::IsDataCachedToEndOfResource(int64_t aOffset)
  1094   return mCacheStream.IsDataCachedToEndOfStream(aOffset);
  1097 void
  1098 ChannelMediaResource::EnsureCacheUpToDate()
  1100   mCacheStream.EnsureCacheUpdate();
  1103 bool
  1104 ChannelMediaResource::IsSuspendedByCache()
  1106   return mCacheStream.AreAllStreamsForResourceSuspended();
  1109 bool
  1110 ChannelMediaResource::IsSuspended()
  1112   MutexAutoLock lock(mLock);
  1113   return mSuspendCount > 0;
  1116 void
  1117 ChannelMediaResource::SetReadMode(MediaCacheStream::ReadMode aMode)
  1119   mCacheStream.SetReadMode(aMode);
  1122 void
  1123 ChannelMediaResource::SetPlaybackRate(uint32_t aBytesPerSecond)
  1125   mCacheStream.SetPlaybackRate(aBytesPerSecond);
  1128 void
  1129 ChannelMediaResource::Pin()
  1131   mCacheStream.Pin();
  1134 void
  1135 ChannelMediaResource::Unpin()
  1137   mCacheStream.Unpin();
  1140 double
  1141 ChannelMediaResource::GetDownloadRate(bool* aIsReliable)
  1143   MutexAutoLock lock(mLock);
  1144   return mChannelStatistics->GetRate(aIsReliable);
  1147 int64_t
  1148 ChannelMediaResource::GetLength()
  1150   return mCacheStream.GetLength();
  1153 void
  1154 ChannelMediaResource::PossiblySuspend()
  1156   bool isPending = false;
  1157   nsresult rv = mChannel->IsPending(&isPending);
  1158   if (NS_SUCCEEDED(rv) && isPending) {
  1159     mChannel->Suspend();
  1160     mIgnoreResume = false;
  1161   } else {
  1162     mIgnoreResume = true;
  1166 void
  1167 ChannelMediaResource::PossiblyResume()
  1169   if (!mIgnoreResume) {
  1170     mChannel->Resume();
  1171   } else {
  1172     mIgnoreResume = false;
  1176 class FileMediaResource : public BaseMediaResource
  1178 public:
  1179   FileMediaResource(MediaDecoder* aDecoder,
  1180                     nsIChannel* aChannel,
  1181                     nsIURI* aURI,
  1182                     const nsACString& aContentType) :
  1183     BaseMediaResource(aDecoder, aChannel, aURI, aContentType),
  1184     mSize(-1),
  1185     mLock("FileMediaResource.mLock"),
  1186     mSizeInitialized(false)
  1189   ~FileMediaResource()
  1193   // Main thread
  1194   virtual nsresult Open(nsIStreamListener** aStreamListener);
  1195   virtual nsresult Close();
  1196   virtual void     Suspend(bool aCloseImmediately) {}
  1197   virtual void     Resume() {}
  1198   virtual already_AddRefed<nsIPrincipal> GetCurrentPrincipal();
  1199   virtual bool     CanClone();
  1200   virtual already_AddRefed<MediaResource> CloneData(MediaDecoder* aDecoder);
  1201   virtual nsresult ReadFromCache(char* aBuffer, int64_t aOffset, uint32_t aCount);
  1203   // These methods are called off the main thread.
  1205   // Other thread
  1206   virtual void     SetReadMode(MediaCacheStream::ReadMode aMode) {}
  1207   virtual void     SetPlaybackRate(uint32_t aBytesPerSecond) {}
  1208   virtual nsresult Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes);
  1209   virtual nsresult ReadAt(int64_t aOffset, char* aBuffer,
  1210                           uint32_t aCount, uint32_t* aBytes);
  1211   virtual nsresult Seek(int32_t aWhence, int64_t aOffset);
  1212   virtual void     StartSeekingForMetadata() {};
  1213   virtual void     EndSeekingForMetadata() {};
  1214   virtual int64_t  Tell();
  1216   // Any thread
  1217   virtual void    Pin() {}
  1218   virtual void    Unpin() {}
  1219   virtual double  GetDownloadRate(bool* aIsReliable)
  1221     // The data's all already here
  1222     *aIsReliable = true;
  1223     return 100*1024*1024; // arbitray, use 100MB/s
  1225   virtual int64_t GetLength() {
  1226     MutexAutoLock lock(mLock);
  1228     EnsureSizeInitialized();
  1229     return mSizeInitialized ? mSize : 0;
  1231   virtual int64_t GetNextCachedData(int64_t aOffset)
  1233     MutexAutoLock lock(mLock);
  1235     EnsureSizeInitialized();
  1236     return (aOffset < mSize) ? aOffset : -1;
  1238   virtual int64_t GetCachedDataEnd(int64_t aOffset) {
  1239     MutexAutoLock lock(mLock);
  1241     EnsureSizeInitialized();
  1242     return std::max(aOffset, mSize);
  1244   virtual bool    IsDataCachedToEndOfResource(int64_t aOffset) { return true; }
  1245   virtual bool    IsSuspendedByCache() { return false; }
  1246   virtual bool    IsSuspended() { return false; }
  1247   virtual bool    IsTransportSeekable() MOZ_OVERRIDE { return true; }
  1249   nsresult GetCachedRanges(nsTArray<MediaByteRange>& aRanges);
  1251   virtual size_t SizeOfExcludingThis(
  1252                         MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
  1254     // Might be useful to track in the future:
  1255     // - mInput
  1256     return BaseMediaResource::SizeOfExcludingThis(aMallocSizeOf);
  1259   virtual size_t SizeOfIncludingThis(
  1260                         MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
  1262     return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
  1265 protected:
  1266   // These Unsafe variants of Read and Seek perform their operations
  1267   // without acquiring mLock. The caller must obtain the lock before
  1268   // calling. The implmentation of Read, Seek and ReadAt obtains the
  1269   // lock before calling these Unsafe variants to read or seek.
  1270   nsresult UnsafeRead(char* aBuffer, uint32_t aCount, uint32_t* aBytes);
  1271   nsresult UnsafeSeek(int32_t aWhence, int64_t aOffset);
  1272 private:
  1273   // Ensures mSize is initialized, if it can be.
  1274   // mLock must be held when this is called, and mInput must be non-null.
  1275   void EnsureSizeInitialized();
  1277   // The file size, or -1 if not known. Immutable after Open().
  1278   // Can be used from any thread.
  1279   int64_t mSize;
  1281   // This lock handles synchronisation between calls to Close() and
  1282   // the Read, Seek, etc calls. Close must not be called while a
  1283   // Read or Seek is in progress since it resets various internal
  1284   // values to null.
  1285   // This lock protects mSeekable, mInput, mSize, and mSizeInitialized.
  1286   Mutex mLock;
  1288   // Seekable stream interface to file. This can be used from any
  1289   // thread.
  1290   nsCOMPtr<nsISeekableStream> mSeekable;
  1292   // Input stream for the media data. This can be used from any
  1293   // thread.
  1294   nsCOMPtr<nsIInputStream>  mInput;
  1296   // Whether we've attempted to initialize mSize. Note that mSize can be -1
  1297   // when mSizeInitialized is true if we tried and failed to get the size
  1298   // of the file.
  1299   bool mSizeInitialized;
  1300 };
  1302 void FileMediaResource::EnsureSizeInitialized()
  1304   mLock.AssertCurrentThreadOwns();
  1305   NS_ASSERTION(mInput, "Must have file input stream");
  1306   if (mSizeInitialized) {
  1307     return;
  1309   mSizeInitialized = true;
  1310   // Get the file size and inform the decoder.
  1311   uint64_t size;
  1312   nsresult res = mInput->Available(&size);
  1313   if (NS_SUCCEEDED(res) && size <= INT64_MAX) {
  1314     mSize = (int64_t)size;
  1315     nsCOMPtr<nsIRunnable> event = new DataEnded(mDecoder, NS_OK);
  1316     NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
  1320 nsresult FileMediaResource::GetCachedRanges(nsTArray<MediaByteRange>& aRanges)
  1322   MutexAutoLock lock(mLock);
  1324   EnsureSizeInitialized();
  1325   if (mSize == -1) {
  1326     return NS_ERROR_FAILURE;
  1328   aRanges.AppendElement(MediaByteRange(0, mSize));
  1329   return NS_OK;
  1332 nsresult FileMediaResource::Open(nsIStreamListener** aStreamListener)
  1334   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
  1336   if (aStreamListener) {
  1337     *aStreamListener = nullptr;
  1340   nsresult rv = NS_OK;
  1341   if (aStreamListener) {
  1342     // The channel is already open. We need a synchronous stream that
  1343     // implements nsISeekableStream, so we have to find the underlying
  1344     // file and reopen it
  1345     nsCOMPtr<nsIFileChannel> fc(do_QueryInterface(mChannel));
  1346     if (fc) {
  1347       nsCOMPtr<nsIFile> file;
  1348       rv = fc->GetFile(getter_AddRefs(file));
  1349       NS_ENSURE_SUCCESS(rv, rv);
  1351       rv = NS_NewLocalFileInputStream(getter_AddRefs(mInput), file);
  1352     } else if (IsBlobURI(mURI)) {
  1353       rv = NS_GetStreamForBlobURI(mURI, getter_AddRefs(mInput));
  1355   } else {
  1356     // Ensure that we never load a local file from some page on a
  1357     // web server.
  1358     MediaDecoderOwner* owner = mDecoder->GetMediaOwner();
  1359     NS_ENSURE_TRUE(owner, NS_ERROR_FAILURE);
  1360     dom::HTMLMediaElement* element = owner->GetMediaElement();
  1361     NS_ENSURE_TRUE(element, NS_ERROR_FAILURE);
  1363     rv = nsContentUtils::GetSecurityManager()->
  1364            CheckLoadURIWithPrincipal(element->NodePrincipal(),
  1365                                      mURI,
  1366                                      nsIScriptSecurityManager::STANDARD);
  1367     NS_ENSURE_SUCCESS(rv, rv);
  1369     rv = mChannel->Open(getter_AddRefs(mInput));
  1371   NS_ENSURE_SUCCESS(rv, rv);
  1373   mSeekable = do_QueryInterface(mInput);
  1374   if (!mSeekable) {
  1375     // XXX The file may just be a .url or similar
  1376     // shortcut that points to a Web site. We need to fix this by
  1377     // doing an async open and waiting until we locate the real resource,
  1378     // then using that (if it's still a file!).
  1379     return NS_ERROR_FAILURE;
  1382   return NS_OK;
  1385 nsresult FileMediaResource::Close()
  1387   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
  1389   // Since mChennel is only accessed by main thread, there is no necessary to
  1390   // take the lock.
  1391   if (mChannel) {
  1392     mChannel->Cancel(NS_ERROR_PARSED_DATA_CACHED);
  1393     mChannel = nullptr;
  1396   return NS_OK;
  1399 already_AddRefed<nsIPrincipal> FileMediaResource::GetCurrentPrincipal()
  1401   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
  1403   nsCOMPtr<nsIPrincipal> principal;
  1404   nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
  1405   if (!secMan || !mChannel)
  1406     return nullptr;
  1407   secMan->GetChannelPrincipal(mChannel, getter_AddRefs(principal));
  1408   return principal.forget();
  1411 bool FileMediaResource::CanClone()
  1413   return true;
  1416 already_AddRefed<MediaResource> FileMediaResource::CloneData(MediaDecoder* aDecoder)
  1418   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
  1420   MediaDecoderOwner* owner = mDecoder->GetMediaOwner();
  1421   if (!owner) {
  1422     // The decoder is being shut down, so we can't clone
  1423     return nullptr;
  1425   dom::HTMLMediaElement* element = owner->GetMediaElement();
  1426   if (!element) {
  1427     // The decoder is being shut down, so we can't clone
  1428     return nullptr;
  1430   nsCOMPtr<nsILoadGroup> loadGroup = element->GetDocumentLoadGroup();
  1431   NS_ENSURE_TRUE(loadGroup, nullptr);
  1433   nsCOMPtr<nsIChannel> channel;
  1434   nsresult rv =
  1435     NS_NewChannel(getter_AddRefs(channel), mURI, nullptr, loadGroup, nullptr, 0);
  1436   if (NS_FAILED(rv))
  1437     return nullptr;
  1439   nsRefPtr<MediaResource> resource(new FileMediaResource(aDecoder, channel, mURI, GetContentType()));
  1440   return resource.forget();
  1443 nsresult FileMediaResource::ReadFromCache(char* aBuffer, int64_t aOffset, uint32_t aCount)
  1445   MutexAutoLock lock(mLock);
  1447   EnsureSizeInitialized();
  1448   int64_t offset = 0;
  1449   nsresult res = mSeekable->Tell(&offset);
  1450   NS_ENSURE_SUCCESS(res,res);
  1451   res = mSeekable->Seek(nsISeekableStream::NS_SEEK_SET, aOffset);
  1452   NS_ENSURE_SUCCESS(res,res);
  1453   uint32_t bytesRead = 0;
  1454   do {
  1455     uint32_t x = 0;
  1456     uint32_t bytesToRead = aCount - bytesRead;
  1457     res = mInput->Read(aBuffer, bytesToRead, &x);
  1458     bytesRead += x;
  1459   } while (bytesRead != aCount && res == NS_OK);
  1461   // Reset read head to original position so we don't disturb any other
  1462   // reading thread.
  1463   nsresult seekres = mSeekable->Seek(nsISeekableStream::NS_SEEK_SET, offset);
  1465   // If a read failed in the loop above, we want to return its failure code.
  1466   NS_ENSURE_SUCCESS(res,res);
  1468   // Else we succeed if the reset-seek succeeds.
  1469   return seekres;
  1472 nsresult FileMediaResource::Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes)
  1474   nsresult rv;
  1475   int64_t offset = 0;
  1477     MutexAutoLock lock(mLock);
  1478     mSeekable->Tell(&offset);
  1479     rv = UnsafeRead(aBuffer, aCount, aBytes);
  1481   if (NS_SUCCEEDED(rv)) {
  1482     DispatchBytesConsumed(*aBytes, offset);
  1484   return rv;
  1487 nsresult FileMediaResource::UnsafeRead(char* aBuffer, uint32_t aCount, uint32_t* aBytes)
  1489   EnsureSizeInitialized();
  1490   return mInput->Read(aBuffer, aCount, aBytes);
  1493 nsresult FileMediaResource::ReadAt(int64_t aOffset, char* aBuffer,
  1494                                    uint32_t aCount, uint32_t* aBytes)
  1496   NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
  1498   nsresult rv;
  1500     MutexAutoLock lock(mLock);
  1501     rv = UnsafeSeek(nsISeekableStream::NS_SEEK_SET, aOffset);
  1502     if (NS_FAILED(rv)) return rv;
  1503     rv = UnsafeRead(aBuffer, aCount, aBytes);
  1505   if (NS_SUCCEEDED(rv)) {
  1506     DispatchBytesConsumed(*aBytes, aOffset);
  1508   return rv;
  1511 nsresult FileMediaResource::Seek(int32_t aWhence, int64_t aOffset)
  1513   NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
  1515   MutexAutoLock lock(mLock);
  1516   return UnsafeSeek(aWhence, aOffset);
  1519 nsresult FileMediaResource::UnsafeSeek(int32_t aWhence, int64_t aOffset)
  1521   NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
  1523   if (!mSeekable)
  1524     return NS_ERROR_FAILURE;
  1525   EnsureSizeInitialized();
  1526   return mSeekable->Seek(aWhence, aOffset);
  1529 int64_t FileMediaResource::Tell()
  1531   NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
  1533   MutexAutoLock lock(mLock);
  1534   if (!mSeekable)
  1535     return 0;
  1536   EnsureSizeInitialized();
  1538   int64_t offset = 0;
  1539   mSeekable->Tell(&offset);
  1540   return offset;
  1543 already_AddRefed<MediaResource>
  1544 MediaResource::Create(MediaDecoder* aDecoder, nsIChannel* aChannel)
  1546   NS_ASSERTION(NS_IsMainThread(),
  1547                "MediaResource::Open called on non-main thread");
  1549   // If the channel was redirected, we want the post-redirect URI;
  1550   // but if the URI scheme was expanded, say from chrome: to jar:file:,
  1551   // we want the original URI.
  1552   nsCOMPtr<nsIURI> uri;
  1553   nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
  1554   NS_ENSURE_SUCCESS(rv, nullptr);
  1556   nsAutoCString contentType;
  1557   aChannel->GetContentType(contentType);
  1559   nsCOMPtr<nsIFileChannel> fc = do_QueryInterface(aChannel);
  1560   nsRefPtr<MediaResource> resource;
  1561   if (fc || IsBlobURI(uri)) {
  1562     resource = new FileMediaResource(aDecoder, aChannel, uri, contentType);
  1563   } else if (IsRtspURI(uri)) {
  1564     resource = new RtspMediaResource(aDecoder, aChannel, uri, contentType);
  1565   } else {
  1566     resource = new ChannelMediaResource(aDecoder, aChannel, uri, contentType);
  1568   return resource.forget();
  1571 void BaseMediaResource::MoveLoadsToBackground() {
  1572   NS_ASSERTION(!mLoadInBackground, "Why are you calling this more than once?");
  1573   mLoadInBackground = true;
  1574   if (!mChannel) {
  1575     // No channel, resource is probably already loaded.
  1576     return;
  1579   MediaDecoderOwner* owner = mDecoder->GetMediaOwner();
  1580   if (!owner) {
  1581     NS_WARNING("Null owner in MediaResource::MoveLoadsToBackground()");
  1582     return;
  1584   dom::HTMLMediaElement* element = owner->GetMediaElement();
  1585   if (!element) {
  1586     NS_WARNING("Null element in MediaResource::MoveLoadsToBackground()");
  1587     return;
  1590   bool isPending = false;
  1591   if (NS_SUCCEEDED(mChannel->IsPending(&isPending)) &&
  1592       isPending) {
  1593     nsLoadFlags loadFlags;
  1594     DebugOnly<nsresult> rv = mChannel->GetLoadFlags(&loadFlags);
  1595     NS_ASSERTION(NS_SUCCEEDED(rv), "GetLoadFlags() failed!");
  1597     loadFlags |= nsIRequest::LOAD_BACKGROUND;
  1598     ModifyLoadFlags(loadFlags);
  1602 void BaseMediaResource::ModifyLoadFlags(nsLoadFlags aFlags)
  1604   nsCOMPtr<nsILoadGroup> loadGroup;
  1605   DebugOnly<nsresult> rv = mChannel->GetLoadGroup(getter_AddRefs(loadGroup));
  1606   NS_ASSERTION(NS_SUCCEEDED(rv), "GetLoadGroup() failed!");
  1608   nsresult status;
  1609   mChannel->GetStatus(&status);
  1611   // Note: if (NS_FAILED(status)), the channel won't be in the load group.
  1612   if (loadGroup &&
  1613       NS_SUCCEEDED(status)) {
  1614     rv = loadGroup->RemoveRequest(mChannel, nullptr, status);
  1615     NS_ASSERTION(NS_SUCCEEDED(rv), "RemoveRequest() failed!");
  1618   rv = mChannel->SetLoadFlags(aFlags);
  1619   NS_ASSERTION(NS_SUCCEEDED(rv), "SetLoadFlags() failed!");
  1621   if (loadGroup &&
  1622       NS_SUCCEEDED(status)) {
  1623     rv = loadGroup->AddRequest(mChannel, nullptr);
  1624     NS_ASSERTION(NS_SUCCEEDED(rv), "AddRequest() failed!");
  1628 class DispatchBytesConsumedEvent : public nsRunnable {
  1629 public:
  1630   DispatchBytesConsumedEvent(MediaDecoder* aDecoder,
  1631                              int64_t aNumBytes,
  1632                              int64_t aOffset)
  1633     : mDecoder(aDecoder),
  1634       mNumBytes(aNumBytes),
  1635       mOffset(aOffset)
  1637     MOZ_COUNT_CTOR(DispatchBytesConsumedEvent);
  1640   ~DispatchBytesConsumedEvent()
  1642     MOZ_COUNT_DTOR(DispatchBytesConsumedEvent);
  1645   NS_IMETHOD Run() {
  1646     mDecoder->NotifyBytesConsumed(mNumBytes, mOffset);
  1647     // Drop ref to decoder on main thread, just in case this reference
  1648     // ends up being the last owning reference somehow.
  1649     mDecoder = nullptr;
  1650     return NS_OK;
  1653   RefPtr<MediaDecoder> mDecoder;
  1654   int64_t mNumBytes;
  1655   int64_t mOffset;
  1656 };
  1658 void BaseMediaResource::DispatchBytesConsumed(int64_t aNumBytes, int64_t aOffset)
  1660   if (aNumBytes <= 0) {
  1661     return;
  1663   RefPtr<nsIRunnable> event(new DispatchBytesConsumedEvent(mDecoder, aNumBytes, aOffset));
  1664   NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
  1667 } // namespace mozilla

mercurial