content/media/MediaResource.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/content/media/MediaResource.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,1668 @@
     1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     1.5 +/* vim:set ts=2 sw=2 sts=2 et cindent: */
     1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.9 +
    1.10 +#include "mozilla/DebugOnly.h"
    1.11 +
    1.12 +#include "MediaResource.h"
    1.13 +#include "RtspMediaResource.h"
    1.14 +
    1.15 +#include "mozilla/Mutex.h"
    1.16 +#include "nsDebug.h"
    1.17 +#include "MediaDecoder.h"
    1.18 +#include "nsNetUtil.h"
    1.19 +#include "nsThreadUtils.h"
    1.20 +#include "nsIFile.h"
    1.21 +#include "nsIFileChannel.h"
    1.22 +#include "nsIHttpChannel.h"
    1.23 +#include "nsISeekableStream.h"
    1.24 +#include "nsIInputStream.h"
    1.25 +#include "nsIRequestObserver.h"
    1.26 +#include "nsIStreamListener.h"
    1.27 +#include "nsIScriptSecurityManager.h"
    1.28 +#include "nsCrossSiteListenerProxy.h"
    1.29 +#include "mozilla/dom/HTMLMediaElement.h"
    1.30 +#include "nsError.h"
    1.31 +#include "nsICachingChannel.h"
    1.32 +#include "nsIAsyncVerifyRedirectCallback.h"
    1.33 +#include "nsContentUtils.h"
    1.34 +#include "nsHostObjectProtocolHandler.h"
    1.35 +#include <algorithm>
    1.36 +#include "nsProxyRelease.h"
    1.37 +
    1.38 +#ifdef PR_LOGGING
    1.39 +PRLogModuleInfo* gMediaResourceLog;
    1.40 +#define RESOURCE_LOG(msg, ...) PR_LOG(gMediaResourceLog, PR_LOG_DEBUG, \
    1.41 +                                      (msg, ##__VA_ARGS__))
    1.42 +// Debug logging macro with object pointer and class name.
    1.43 +#define CMLOG(msg, ...) \
    1.44 +        RESOURCE_LOG("%p [ChannelMediaResource]: " msg, this, ##__VA_ARGS__)
    1.45 +#else
    1.46 +#define RESOURCE_LOG(msg, ...)
    1.47 +#define CMLOG(msg, ...)
    1.48 +#endif
    1.49 +
    1.50 +static const uint32_t HTTP_OK_CODE = 200;
    1.51 +static const uint32_t HTTP_PARTIAL_RESPONSE_CODE = 206;
    1.52 +
    1.53 +namespace mozilla {
    1.54 +
    1.55 +void
    1.56 +MediaResource::Destroy()
    1.57 +{
    1.58 +  // If we're being destroyed on a non-main thread, we AddRef again and
    1.59 +  // use a proxy to release the MediaResource on the main thread, where
    1.60 +  // the MediaResource is deleted. This ensures we only delete the
    1.61 +  // MediaResource on the main thread.
    1.62 +  if (!NS_IsMainThread()) {
    1.63 +    nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
    1.64 +    NS_ENSURE_TRUE_VOID(mainThread);
    1.65 +    nsRefPtr<MediaResource> doomed(this);
    1.66 +    if (NS_FAILED(NS_ProxyRelease(mainThread, doomed, true))) {
    1.67 +      NS_WARNING("Failed to proxy release to main thread!");
    1.68 +    }
    1.69 +  } else {
    1.70 +    delete this;
    1.71 +  }
    1.72 +}
    1.73 +
    1.74 +NS_IMPL_ADDREF(MediaResource)
    1.75 +NS_IMPL_RELEASE_WITH_DESTROY(MediaResource, Destroy())
    1.76 +NS_IMPL_QUERY_INTERFACE0(MediaResource)
    1.77 +
    1.78 +ChannelMediaResource::ChannelMediaResource(MediaDecoder* aDecoder,
    1.79 +                                           nsIChannel* aChannel,
    1.80 +                                           nsIURI* aURI,
    1.81 +                                           const nsACString& aContentType)
    1.82 +  : BaseMediaResource(aDecoder, aChannel, aURI, aContentType),
    1.83 +    mOffset(0), mSuspendCount(0),
    1.84 +    mReopenOnError(false), mIgnoreClose(false),
    1.85 +    mCacheStream(MOZ_THIS_IN_INITIALIZER_LIST()),
    1.86 +    mLock("ChannelMediaResource.mLock"),
    1.87 +    mIgnoreResume(false),
    1.88 +    mSeekingForMetadata(false),
    1.89 +    mIsTransportSeekable(true)
    1.90 +{
    1.91 +#ifdef PR_LOGGING
    1.92 +  if (!gMediaResourceLog) {
    1.93 +    gMediaResourceLog = PR_NewLogModule("MediaResource");
    1.94 +  }
    1.95 +#endif
    1.96 +}
    1.97 +
    1.98 +ChannelMediaResource::~ChannelMediaResource()
    1.99 +{
   1.100 +  if (mListener) {
   1.101 +    // Kill its reference to us since we're going away
   1.102 +    mListener->Revoke();
   1.103 +  }
   1.104 +}
   1.105 +
   1.106 +// ChannelMediaResource::Listener just observes the channel and
   1.107 +// forwards notifications to the ChannelMediaResource. We use multiple
   1.108 +// listener objects so that when we open a new stream for a seek we can
   1.109 +// disconnect the old listener from the ChannelMediaResource and hook up
   1.110 +// a new listener, so notifications from the old channel are discarded
   1.111 +// and don't confuse us.
   1.112 +NS_IMPL_ISUPPORTS(ChannelMediaResource::Listener,
   1.113 +                  nsIRequestObserver, nsIStreamListener, nsIChannelEventSink,
   1.114 +                  nsIInterfaceRequestor)
   1.115 +
   1.116 +nsresult
   1.117 +ChannelMediaResource::Listener::OnStartRequest(nsIRequest* aRequest,
   1.118 +                                               nsISupports* aContext)
   1.119 +{
   1.120 +  if (!mResource)
   1.121 +    return NS_OK;
   1.122 +  return mResource->OnStartRequest(aRequest);
   1.123 +}
   1.124 +
   1.125 +nsresult
   1.126 +ChannelMediaResource::Listener::OnStopRequest(nsIRequest* aRequest,
   1.127 +                                              nsISupports* aContext,
   1.128 +                                              nsresult aStatus)
   1.129 +{
   1.130 +  if (!mResource)
   1.131 +    return NS_OK;
   1.132 +  return mResource->OnStopRequest(aRequest, aStatus);
   1.133 +}
   1.134 +
   1.135 +nsresult
   1.136 +ChannelMediaResource::Listener::OnDataAvailable(nsIRequest* aRequest,
   1.137 +                                                nsISupports* aContext,
   1.138 +                                                nsIInputStream* aStream,
   1.139 +                                                uint64_t aOffset,
   1.140 +                                                uint32_t aCount)
   1.141 +{
   1.142 +  if (!mResource)
   1.143 +    return NS_OK;
   1.144 +  return mResource->OnDataAvailable(aRequest, aStream, aCount);
   1.145 +}
   1.146 +
   1.147 +nsresult
   1.148 +ChannelMediaResource::Listener::AsyncOnChannelRedirect(nsIChannel* aOldChannel,
   1.149 +                                                       nsIChannel* aNewChannel,
   1.150 +                                                       uint32_t aFlags,
   1.151 +                                                       nsIAsyncVerifyRedirectCallback* cb)
   1.152 +{
   1.153 +  nsresult rv = NS_OK;
   1.154 +  if (mResource)
   1.155 +    rv = mResource->OnChannelRedirect(aOldChannel, aNewChannel, aFlags);
   1.156 +
   1.157 +  if (NS_FAILED(rv))
   1.158 +    return rv;
   1.159 +
   1.160 +  cb->OnRedirectVerifyCallback(NS_OK);
   1.161 +  return NS_OK;
   1.162 +}
   1.163 +
   1.164 +nsresult
   1.165 +ChannelMediaResource::Listener::GetInterface(const nsIID & aIID, void **aResult)
   1.166 +{
   1.167 +  return QueryInterface(aIID, aResult);
   1.168 +}
   1.169 +
   1.170 +nsresult
   1.171 +ChannelMediaResource::OnStartRequest(nsIRequest* aRequest)
   1.172 +{
   1.173 +  NS_ASSERTION(mChannel.get() == aRequest, "Wrong channel!");
   1.174 +
   1.175 +  MediaDecoderOwner* owner = mDecoder->GetMediaOwner();
   1.176 +  NS_ENSURE_TRUE(owner, NS_ERROR_FAILURE);
   1.177 +  dom::HTMLMediaElement* element = owner->GetMediaElement();
   1.178 +  NS_ENSURE_TRUE(element, NS_ERROR_FAILURE);
   1.179 +  nsresult status;
   1.180 +  nsresult rv = aRequest->GetStatus(&status);
   1.181 +  NS_ENSURE_SUCCESS(rv, rv);
   1.182 +
   1.183 +  if (status == NS_BINDING_ABORTED) {
   1.184 +    // Request was aborted before we had a chance to receive any data, or
   1.185 +    // even an OnStartRequest(). Close the channel. This is important, as
   1.186 +    // we don't want to mess up our state, as if we're cloned that would
   1.187 +    // cause the clone to copy incorrect metadata (like whether we're
   1.188 +    // infinite for example).
   1.189 +    CloseChannel();
   1.190 +    return status;
   1.191 +  }
   1.192 +
   1.193 +  if (element->ShouldCheckAllowOrigin()) {
   1.194 +    // If the request was cancelled by nsCORSListenerProxy due to failing
   1.195 +    // the CORS security check, send an error through to the media element.
   1.196 +    if (status == NS_ERROR_DOM_BAD_URI) {
   1.197 +      mDecoder->NetworkError();
   1.198 +      return NS_ERROR_DOM_BAD_URI;
   1.199 +    }
   1.200 +  }
   1.201 +
   1.202 +  nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(aRequest);
   1.203 +  bool seekable = false;
   1.204 +  if (hc) {
   1.205 +    uint32_t responseStatus = 0;
   1.206 +    hc->GetResponseStatus(&responseStatus);
   1.207 +    bool succeeded = false;
   1.208 +    hc->GetRequestSucceeded(&succeeded);
   1.209 +
   1.210 +    if (!succeeded && NS_SUCCEEDED(status)) {
   1.211 +      // HTTP-level error (e.g. 4xx); treat this as a fatal network-level error.
   1.212 +      // We might get this on a seek.
   1.213 +      // (Note that lower-level errors indicated by NS_FAILED(status) are
   1.214 +      // handled in OnStopRequest.)
   1.215 +      // A 416 error should treated as EOF here... it's possible
   1.216 +      // that we don't get Content-Length, we read N bytes, then we
   1.217 +      // suspend and resume, the resume reopens the channel and we seek to
   1.218 +      // offset N, but there are no more bytes, so we get a 416
   1.219 +      // "Requested Range Not Satisfiable".
   1.220 +      if (responseStatus == HTTP_REQUESTED_RANGE_NOT_SATISFIABLE_CODE) {
   1.221 +        // OnStopRequest will not be fired, so we need to do some of its
   1.222 +        // work here.
   1.223 +        mCacheStream.NotifyDataEnded(status);
   1.224 +      } else {
   1.225 +        mDecoder->NetworkError();
   1.226 +      }
   1.227 +
   1.228 +      // This disconnects our listener so we don't get any more data. We
   1.229 +      // certainly don't want an error page to end up in our cache!
   1.230 +      CloseChannel();
   1.231 +      return NS_OK;
   1.232 +    }
   1.233 +
   1.234 +    nsAutoCString ranges;
   1.235 +    hc->GetResponseHeader(NS_LITERAL_CSTRING("Accept-Ranges"),
   1.236 +                          ranges);
   1.237 +    bool acceptsRanges = ranges.EqualsLiteral("bytes");
   1.238 +    // True if this channel will not return an unbounded amount of data
   1.239 +    bool dataIsBounded = false;
   1.240 +
   1.241 +    int64_t contentLength = -1;
   1.242 +    hc->GetContentLength(&contentLength);
   1.243 +    if (contentLength >= 0 && responseStatus == HTTP_OK_CODE) {
   1.244 +      // "OK" status means Content-Length is for the whole resource.
   1.245 +      // Since that's bounded, we know we have a finite-length resource.
   1.246 +      dataIsBounded = true;
   1.247 +    }
   1.248 +
   1.249 +    if (mOffset == 0) {
   1.250 +      // Look for duration headers from known Ogg content systems.
   1.251 +      // In the case of multiple options for obtaining the duration
   1.252 +      // the order of precedence is:
   1.253 +      // 1) The Media resource metadata if possible (done by the decoder itself).
   1.254 +      // 2) Content-Duration message header.
   1.255 +      // 3) X-AMZ-Meta-Content-Duration.
   1.256 +      // 4) X-Content-Duration.
   1.257 +      // 5) Perform a seek in the decoder to find the value.
   1.258 +      nsAutoCString durationText;
   1.259 +      nsresult ec = NS_OK;
   1.260 +      rv = hc->GetResponseHeader(NS_LITERAL_CSTRING("Content-Duration"), durationText);
   1.261 +      if (NS_FAILED(rv)) {
   1.262 +        rv = hc->GetResponseHeader(NS_LITERAL_CSTRING("X-AMZ-Meta-Content-Duration"), durationText);
   1.263 +      }
   1.264 +      if (NS_FAILED(rv)) {
   1.265 +        rv = hc->GetResponseHeader(NS_LITERAL_CSTRING("X-Content-Duration"), durationText);
   1.266 +      }
   1.267 +
   1.268 +      // If there is a Content-Duration header with a valid value, record
   1.269 +      // the duration.
   1.270 +      if (NS_SUCCEEDED(rv)) {
   1.271 +        double duration = durationText.ToDouble(&ec);
   1.272 +        if (ec == NS_OK && duration >= 0) {
   1.273 +          mDecoder->SetDuration(duration);
   1.274 +          // We know the resource must be bounded.
   1.275 +          dataIsBounded = true;
   1.276 +        }
   1.277 +      }
   1.278 +    }
   1.279 +
   1.280 +    // Assume Range requests have a bounded upper limit unless the
   1.281 +    // Content-Range header tells us otherwise.
   1.282 +    bool boundedSeekLimit = true;
   1.283 +    // Check response code for byte-range requests (seeking, chunk requests).
   1.284 +    if (!mByteRange.IsNull() && (responseStatus == HTTP_PARTIAL_RESPONSE_CODE)) {
   1.285 +      // Parse Content-Range header.
   1.286 +      int64_t rangeStart = 0;
   1.287 +      int64_t rangeEnd = 0;
   1.288 +      int64_t rangeTotal = 0;
   1.289 +      rv = ParseContentRangeHeader(hc, rangeStart, rangeEnd, rangeTotal);
   1.290 +      if (NS_FAILED(rv)) {
   1.291 +        // Content-Range header text should be parse-able.
   1.292 +        CMLOG("Error processing \'Content-Range' for "
   1.293 +              "HTTP_PARTIAL_RESPONSE_CODE: rv[%x] channel[%p] decoder[%p]",
   1.294 +              rv, hc.get(), mDecoder);
   1.295 +        mDecoder->NetworkError();
   1.296 +        CloseChannel();
   1.297 +        return NS_OK;
   1.298 +      }
   1.299 +
   1.300 +      // Give some warnings if the ranges are unexpected.
   1.301 +      // XXX These could be error conditions.
   1.302 +      NS_WARN_IF_FALSE(mByteRange.mStart == rangeStart,
   1.303 +                       "response range start does not match request");
   1.304 +      NS_WARN_IF_FALSE(mOffset == rangeStart,
   1.305 +                       "response range start does not match current offset");
   1.306 +      NS_WARN_IF_FALSE(mByteRange.mEnd == rangeEnd,
   1.307 +                       "response range end does not match request");
   1.308 +      // Notify media cache about the length and start offset of data received.
   1.309 +      // Note: If aRangeTotal == -1, then the total bytes is unknown at this stage.
   1.310 +      //       For now, tell the decoder that the stream is infinite.
   1.311 +      if (rangeTotal == -1) {
   1.312 +        boundedSeekLimit = false;
   1.313 +      } else {
   1.314 +        mCacheStream.NotifyDataLength(rangeTotal);
   1.315 +      }
   1.316 +      mCacheStream.NotifyDataStarted(rangeStart);
   1.317 +
   1.318 +      mOffset = rangeStart;
   1.319 +      // We received 'Content-Range', so the server accepts range requests.
   1.320 +      acceptsRanges = true;
   1.321 +    } else if (((mOffset > 0) || !mByteRange.IsNull())
   1.322 +               && (responseStatus == HTTP_OK_CODE)) {
   1.323 +      // If we get an OK response but we were seeking, or requesting a byte
   1.324 +      // range, then we have to assume that seeking doesn't work. We also need
   1.325 +      // to tell the cache that it's getting data for the start of the stream.
   1.326 +      mCacheStream.NotifyDataStarted(0);
   1.327 +      mOffset = 0;
   1.328 +
   1.329 +      // The server claimed it supported range requests.  It lied.
   1.330 +      acceptsRanges = false;
   1.331 +    } else if (mOffset == 0 &&
   1.332 +               (responseStatus == HTTP_OK_CODE ||
   1.333 +                responseStatus == HTTP_PARTIAL_RESPONSE_CODE)) {
   1.334 +      if (contentLength >= 0) {
   1.335 +        mCacheStream.NotifyDataLength(contentLength);
   1.336 +      }
   1.337 +    }
   1.338 +    // XXX we probably should examine the Content-Range header in case
   1.339 +    // the server gave us a range which is not quite what we asked for
   1.340 +
   1.341 +    // If we get an HTTP_OK_CODE response to our byte range request,
   1.342 +    // and the server isn't sending Accept-Ranges:bytes then we don't
   1.343 +    // support seeking.
   1.344 +    seekable =
   1.345 +      responseStatus == HTTP_PARTIAL_RESPONSE_CODE || acceptsRanges;
   1.346 +    if (seekable && boundedSeekLimit) {
   1.347 +      // If range requests are supported, and we did not see an unbounded
   1.348 +      // upper range limit, we assume the resource is bounded.
   1.349 +      dataIsBounded = true;
   1.350 +    }
   1.351 +
   1.352 +    mDecoder->SetInfinite(!dataIsBounded);
   1.353 +  }
   1.354 +  mDecoder->SetTransportSeekable(seekable);
   1.355 +  mCacheStream.SetTransportSeekable(seekable);
   1.356 +
   1.357 +  {
   1.358 +    MutexAutoLock lock(mLock);
   1.359 +    mIsTransportSeekable = seekable;
   1.360 +    mChannelStatistics->Start();
   1.361 +  }
   1.362 +
   1.363 +  mReopenOnError = false;
   1.364 +  // If we are seeking to get metadata, because we are playing an OGG file,
   1.365 +  // ignore if the channel gets closed without us suspending it explicitly. We
   1.366 +  // don't want to tell the element that the download has finished whereas we
   1.367 +  // just happended to have reached the end of the media while seeking.
   1.368 +  mIgnoreClose = mSeekingForMetadata;
   1.369 +
   1.370 +  if (mSuspendCount > 0) {
   1.371 +    // Re-suspend the channel if it needs to be suspended
   1.372 +    // No need to call PossiblySuspend here since the channel is
   1.373 +    // definitely in the right state for us in OnStartRequest.
   1.374 +    mChannel->Suspend();
   1.375 +    mIgnoreResume = false;
   1.376 +  }
   1.377 +
   1.378 +  // Fires an initial progress event and sets up the stall counter so stall events
   1.379 +  // fire if no download occurs within the required time frame.
   1.380 +  mDecoder->Progress(false);
   1.381 +
   1.382 +  return NS_OK;
   1.383 +}
   1.384 +
   1.385 +bool
   1.386 +ChannelMediaResource::IsTransportSeekable()
   1.387 +{
   1.388 +  MutexAutoLock lock(mLock);
   1.389 +  return mIsTransportSeekable;
   1.390 +}
   1.391 +
   1.392 +nsresult
   1.393 +ChannelMediaResource::ParseContentRangeHeader(nsIHttpChannel * aHttpChan,
   1.394 +                                              int64_t& aRangeStart,
   1.395 +                                              int64_t& aRangeEnd,
   1.396 +                                              int64_t& aRangeTotal)
   1.397 +{
   1.398 +  NS_ENSURE_ARG(aHttpChan);
   1.399 +
   1.400 +  nsAutoCString rangeStr;
   1.401 +  nsresult rv = aHttpChan->GetResponseHeader(NS_LITERAL_CSTRING("Content-Range"),
   1.402 +                                             rangeStr);
   1.403 +  NS_ENSURE_SUCCESS(rv, rv);
   1.404 +  NS_ENSURE_FALSE(rangeStr.IsEmpty(), NS_ERROR_ILLEGAL_VALUE);
   1.405 +
   1.406 +  // Parse the range header: e.g. Content-Range: bytes 7000-7999/8000.
   1.407 +  int32_t spacePos = rangeStr.Find(NS_LITERAL_CSTRING(" "));
   1.408 +  int32_t dashPos = rangeStr.Find(NS_LITERAL_CSTRING("-"), true, spacePos);
   1.409 +  int32_t slashPos = rangeStr.Find(NS_LITERAL_CSTRING("/"), true, dashPos);
   1.410 +
   1.411 +  nsAutoCString aRangeStartText;
   1.412 +  rangeStr.Mid(aRangeStartText, spacePos+1, dashPos-(spacePos+1));
   1.413 +  aRangeStart = aRangeStartText.ToInteger64(&rv);
   1.414 +  NS_ENSURE_SUCCESS(rv, rv);
   1.415 +  NS_ENSURE_TRUE(0 <= aRangeStart, NS_ERROR_ILLEGAL_VALUE);
   1.416 +
   1.417 +  nsAutoCString aRangeEndText;
   1.418 +  rangeStr.Mid(aRangeEndText, dashPos+1, slashPos-(dashPos+1));
   1.419 +  aRangeEnd = aRangeEndText.ToInteger64(&rv);
   1.420 +  NS_ENSURE_SUCCESS(rv, rv);
   1.421 +  NS_ENSURE_TRUE(aRangeStart < aRangeEnd, NS_ERROR_ILLEGAL_VALUE);
   1.422 +
   1.423 +  nsAutoCString aRangeTotalText;
   1.424 +  rangeStr.Right(aRangeTotalText, rangeStr.Length()-(slashPos+1));
   1.425 +  if (aRangeTotalText[0] == '*') {
   1.426 +    aRangeTotal = -1;
   1.427 +  } else {
   1.428 +    aRangeTotal = aRangeTotalText.ToInteger64(&rv);
   1.429 +    NS_ENSURE_TRUE(aRangeEnd < aRangeTotal, NS_ERROR_ILLEGAL_VALUE);
   1.430 +    NS_ENSURE_SUCCESS(rv, rv);
   1.431 +  }
   1.432 +
   1.433 +  CMLOG("Received bytes [%lld] to [%lld] of [%lld] for decoder[%p]",
   1.434 +        aRangeStart, aRangeEnd, aRangeTotal, mDecoder);
   1.435 +
   1.436 +  return NS_OK;
   1.437 +}
   1.438 +
   1.439 +nsresult
   1.440 +ChannelMediaResource::OnStopRequest(nsIRequest* aRequest, nsresult aStatus)
   1.441 +{
   1.442 +  NS_ASSERTION(mChannel.get() == aRequest, "Wrong channel!");
   1.443 +  NS_ASSERTION(mSuspendCount == 0,
   1.444 +               "How can OnStopRequest fire while we're suspended?");
   1.445 +
   1.446 +  {
   1.447 +    MutexAutoLock lock(mLock);
   1.448 +    mChannelStatistics->Stop();
   1.449 +  }
   1.450 +
   1.451 +  // Note that aStatus might have succeeded --- this might be a normal close
   1.452 +  // --- even in situations where the server cut us off because we were
   1.453 +  // suspended. So we need to "reopen on error" in that case too. The only
   1.454 +  // cases where we don't need to reopen are when *we* closed the stream.
   1.455 +  // But don't reopen if we need to seek and we don't think we can... that would
   1.456 +  // cause us to just re-read the stream, which would be really bad.
   1.457 +  if (mReopenOnError &&
   1.458 +      aStatus != NS_ERROR_PARSED_DATA_CACHED && aStatus != NS_BINDING_ABORTED &&
   1.459 +      (mOffset == 0 || mCacheStream.IsTransportSeekable())) {
   1.460 +    // If the stream did close normally, then if the server is seekable we'll
   1.461 +    // just seek to the end of the resource and get an HTTP 416 error because
   1.462 +    // there's nothing there, so this isn't bad.
   1.463 +    nsresult rv = CacheClientSeek(mOffset, false);
   1.464 +    if (NS_SUCCEEDED(rv))
   1.465 +      return rv;
   1.466 +    // If the reopen/reseek fails, just fall through and treat this
   1.467 +    // error as fatal.
   1.468 +  }
   1.469 +
   1.470 +  if (!mIgnoreClose) {
   1.471 +    mCacheStream.NotifyDataEnded(aStatus);
   1.472 +
   1.473 +    // Move this request back into the foreground.  This is necessary for
   1.474 +    // requests owned by video documents to ensure the load group fires
   1.475 +    // OnStopRequest when restoring from session history.
   1.476 +    nsLoadFlags loadFlags;
   1.477 +    DebugOnly<nsresult> rv = mChannel->GetLoadFlags(&loadFlags);
   1.478 +    NS_ASSERTION(NS_SUCCEEDED(rv), "GetLoadFlags() failed!");
   1.479 +
   1.480 +    if (loadFlags & nsIRequest::LOAD_BACKGROUND) {
   1.481 +      ModifyLoadFlags(loadFlags & ~nsIRequest::LOAD_BACKGROUND);
   1.482 +    }
   1.483 +  }
   1.484 +
   1.485 +  return NS_OK;
   1.486 +}
   1.487 +
   1.488 +nsresult
   1.489 +ChannelMediaResource::OnChannelRedirect(nsIChannel* aOld, nsIChannel* aNew,
   1.490 +                                        uint32_t aFlags)
   1.491 +{
   1.492 +  mChannel = aNew;
   1.493 +  SetupChannelHeaders();
   1.494 +  return NS_OK;
   1.495 +}
   1.496 +
   1.497 +struct CopySegmentClosure {
   1.498 +  nsCOMPtr<nsIPrincipal> mPrincipal;
   1.499 +  ChannelMediaResource*  mResource;
   1.500 +};
   1.501 +
   1.502 +NS_METHOD
   1.503 +ChannelMediaResource::CopySegmentToCache(nsIInputStream *aInStream,
   1.504 +                                         void *aClosure,
   1.505 +                                         const char *aFromSegment,
   1.506 +                                         uint32_t aToOffset,
   1.507 +                                         uint32_t aCount,
   1.508 +                                         uint32_t *aWriteCount)
   1.509 +{
   1.510 +  CopySegmentClosure* closure = static_cast<CopySegmentClosure*>(aClosure);
   1.511 +
   1.512 +  closure->mResource->mDecoder->NotifyDataArrived(aFromSegment, aCount, closure->mResource->mOffset);
   1.513 +
   1.514 +  // Keep track of where we're up to.
   1.515 +  RESOURCE_LOG("%p [ChannelMediaResource]: CopySegmentToCache at mOffset [%lld] add "
   1.516 +               "[%d] bytes for decoder[%p]",
   1.517 +               closure->mResource, closure->mResource->mOffset, aCount,
   1.518 +               closure->mResource->mDecoder);
   1.519 +  closure->mResource->mOffset += aCount;
   1.520 +
   1.521 +  closure->mResource->mCacheStream.NotifyDataReceived(aCount, aFromSegment,
   1.522 +                                                      closure->mPrincipal);
   1.523 +  *aWriteCount = aCount;
   1.524 +  return NS_OK;
   1.525 +}
   1.526 +
   1.527 +nsresult
   1.528 +ChannelMediaResource::OnDataAvailable(nsIRequest* aRequest,
   1.529 +                                      nsIInputStream* aStream,
   1.530 +                                      uint32_t aCount)
   1.531 +{
   1.532 +  NS_ASSERTION(mChannel.get() == aRequest, "Wrong channel!");
   1.533 +
   1.534 +  {
   1.535 +    MutexAutoLock lock(mLock);
   1.536 +    mChannelStatistics->AddBytes(aCount);
   1.537 +  }
   1.538 +
   1.539 +  CopySegmentClosure closure;
   1.540 +  nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
   1.541 +  if (secMan && mChannel) {
   1.542 +    secMan->GetChannelPrincipal(mChannel, getter_AddRefs(closure.mPrincipal));
   1.543 +  }
   1.544 +  closure.mResource = this;
   1.545 +
   1.546 +  uint32_t count = aCount;
   1.547 +  while (count > 0) {
   1.548 +    uint32_t read;
   1.549 +    nsresult rv = aStream->ReadSegments(CopySegmentToCache, &closure, count,
   1.550 +                                        &read);
   1.551 +    if (NS_FAILED(rv))
   1.552 +      return rv;
   1.553 +    NS_ASSERTION(read > 0, "Read 0 bytes while data was available?");
   1.554 +    count -= read;
   1.555 +  }
   1.556 +
   1.557 +  return NS_OK;
   1.558 +}
   1.559 +
   1.560 +nsresult ChannelMediaResource::Open(nsIStreamListener **aStreamListener)
   1.561 +{
   1.562 +  NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
   1.563 +
   1.564 +  if (!mChannelStatistics) {
   1.565 +    mChannelStatistics = new MediaChannelStatistics();
   1.566 +  }
   1.567 +
   1.568 +  nsresult rv = mCacheStream.Init();
   1.569 +  if (NS_FAILED(rv))
   1.570 +    return rv;
   1.571 +  NS_ASSERTION(mOffset == 0, "Who set mOffset already?");
   1.572 +
   1.573 +  if (!mChannel) {
   1.574 +    // When we're a clone, the decoder might ask us to Open even though
   1.575 +    // we haven't established an mChannel (because we might not need one)
   1.576 +    NS_ASSERTION(!aStreamListener,
   1.577 +                 "Should have already been given a channel if we're to return a stream listener");
   1.578 +    return NS_OK;
   1.579 +  }
   1.580 +
   1.581 +  return OpenChannel(aStreamListener);
   1.582 +}
   1.583 +
   1.584 +nsresult ChannelMediaResource::OpenChannel(nsIStreamListener** aStreamListener)
   1.585 +{
   1.586 +  NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
   1.587 +  NS_ENSURE_TRUE(mChannel, NS_ERROR_NULL_POINTER);
   1.588 +  NS_ASSERTION(!mListener, "Listener should have been removed by now");
   1.589 +
   1.590 +  if (aStreamListener) {
   1.591 +    *aStreamListener = nullptr;
   1.592 +  }
   1.593 +
   1.594 +  if (mByteRange.IsNull()) {
   1.595 +    // We're not making a byte range request, so set the content length,
   1.596 +    // if it's available as an HTTP header. This ensures that MediaResource
   1.597 +    // wrapping objects for platform libraries that expect to know
   1.598 +    // the length of a resource can get it before OnStartRequest() fires.
   1.599 +    nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(mChannel);
   1.600 +    if (hc) {
   1.601 +      int64_t cl = -1;
   1.602 +      if (NS_SUCCEEDED(hc->GetContentLength(&cl)) && cl != -1) {
   1.603 +        mCacheStream.NotifyDataLength(cl);
   1.604 +      }
   1.605 +    }
   1.606 +  }
   1.607 +
   1.608 +  mListener = new Listener(this);
   1.609 +  NS_ENSURE_TRUE(mListener, NS_ERROR_OUT_OF_MEMORY);
   1.610 +
   1.611 +  if (aStreamListener) {
   1.612 +    *aStreamListener = mListener;
   1.613 +    NS_ADDREF(*aStreamListener);
   1.614 +  } else {
   1.615 +    mChannel->SetNotificationCallbacks(mListener.get());
   1.616 +
   1.617 +    nsCOMPtr<nsIStreamListener> listener = mListener.get();
   1.618 +
   1.619 +    // Ensure that if we're loading cross domain, that the server is sending
   1.620 +    // an authorizing Access-Control header.
   1.621 +    MediaDecoderOwner* owner = mDecoder->GetMediaOwner();
   1.622 +    NS_ENSURE_TRUE(owner, NS_ERROR_FAILURE);
   1.623 +    dom::HTMLMediaElement* element = owner->GetMediaElement();
   1.624 +    NS_ENSURE_TRUE(element, NS_ERROR_FAILURE);
   1.625 +    if (element->ShouldCheckAllowOrigin()) {
   1.626 +      nsRefPtr<nsCORSListenerProxy> crossSiteListener =
   1.627 +        new nsCORSListenerProxy(mListener,
   1.628 +                                element->NodePrincipal(),
   1.629 +                                false);
   1.630 +      nsresult rv = crossSiteListener->Init(mChannel);
   1.631 +      listener = crossSiteListener;
   1.632 +      NS_ENSURE_TRUE(crossSiteListener, NS_ERROR_OUT_OF_MEMORY);
   1.633 +      NS_ENSURE_SUCCESS(rv, rv);
   1.634 +    } else {
   1.635 +      nsresult rv = nsContentUtils::GetSecurityManager()->
   1.636 +        CheckLoadURIWithPrincipal(element->NodePrincipal(),
   1.637 +                                  mURI,
   1.638 +                                  nsIScriptSecurityManager::STANDARD);
   1.639 +      NS_ENSURE_SUCCESS(rv, rv);
   1.640 +    }
   1.641 +
   1.642 +    SetupChannelHeaders();
   1.643 +
   1.644 +    nsresult rv = mChannel->AsyncOpen(listener, nullptr);
   1.645 +    NS_ENSURE_SUCCESS(rv, rv);
   1.646 +    // Tell the media element that we are fetching data from a channel.
   1.647 +    element->DownloadResumed(true);
   1.648 +  }
   1.649 +
   1.650 +  return NS_OK;
   1.651 +}
   1.652 +
   1.653 +void ChannelMediaResource::SetupChannelHeaders()
   1.654 +{
   1.655 +  // Always use a byte range request even if we're reading from the start
   1.656 +  // of the resource.
   1.657 +  // This enables us to detect if the stream supports byte range
   1.658 +  // requests, and therefore seeking, early.
   1.659 +  nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(mChannel);
   1.660 +  if (hc) {
   1.661 +    // Use |mByteRange| for a specific chunk, or |mOffset| if seeking in a
   1.662 +    // complete file download.
   1.663 +    nsAutoCString rangeString("bytes=");
   1.664 +    if (!mByteRange.IsNull()) {
   1.665 +      rangeString.AppendInt(mByteRange.mStart);
   1.666 +      mOffset = mByteRange.mStart;
   1.667 +    } else {
   1.668 +      rangeString.AppendInt(mOffset);
   1.669 +    }
   1.670 +    rangeString.Append("-");
   1.671 +    if (!mByteRange.IsNull()) {
   1.672 +      rangeString.AppendInt(mByteRange.mEnd);
   1.673 +    }
   1.674 +    hc->SetRequestHeader(NS_LITERAL_CSTRING("Range"), rangeString, false);
   1.675 +
   1.676 +    // Send Accept header for video and audio types only (Bug 489071)
   1.677 +    NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread");
   1.678 +    MediaDecoderOwner* owner = mDecoder->GetMediaOwner();
   1.679 +    if (!owner) {
   1.680 +      return;
   1.681 +    }
   1.682 +    dom::HTMLMediaElement* element = owner->GetMediaElement();
   1.683 +    if (!element) {
   1.684 +      return;
   1.685 +    }
   1.686 +    element->SetRequestHeaders(hc);
   1.687 +  } else {
   1.688 +    NS_ASSERTION(mOffset == 0, "Don't know how to seek on this channel type");
   1.689 +  }
   1.690 +}
   1.691 +
   1.692 +nsresult ChannelMediaResource::Close()
   1.693 +{
   1.694 +  NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
   1.695 +
   1.696 +  mCacheStream.Close();
   1.697 +  CloseChannel();
   1.698 +  return NS_OK;
   1.699 +}
   1.700 +
   1.701 +already_AddRefed<nsIPrincipal> ChannelMediaResource::GetCurrentPrincipal()
   1.702 +{
   1.703 +  NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
   1.704 +
   1.705 +  nsCOMPtr<nsIPrincipal> principal = mCacheStream.GetCurrentPrincipal();
   1.706 +  return principal.forget();
   1.707 +}
   1.708 +
   1.709 +bool ChannelMediaResource::CanClone()
   1.710 +{
   1.711 +  return mCacheStream.IsAvailableForSharing();
   1.712 +}
   1.713 +
   1.714 +already_AddRefed<MediaResource> ChannelMediaResource::CloneData(MediaDecoder* aDecoder)
   1.715 +{
   1.716 +  NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
   1.717 +  NS_ASSERTION(mCacheStream.IsAvailableForSharing(), "Stream can't be cloned");
   1.718 +
   1.719 +  nsRefPtr<ChannelMediaResource> resource =
   1.720 +    new ChannelMediaResource(aDecoder,
   1.721 +                             nullptr,
   1.722 +                             mURI,
   1.723 +                             GetContentType());
   1.724 +  if (resource) {
   1.725 +    // Initially the clone is treated as suspended by the cache, because
   1.726 +    // we don't have a channel. If the cache needs to read data from the clone
   1.727 +    // it will call CacheClientResume (or CacheClientSeek with aResume true)
   1.728 +    // which will recreate the channel. This way, if all of the media data
   1.729 +    // is already in the cache we don't create an unnecessary HTTP channel
   1.730 +    // and perform a useless HTTP transaction.
   1.731 +    resource->mSuspendCount = 1;
   1.732 +    resource->mCacheStream.InitAsClone(&mCacheStream);
   1.733 +    resource->mChannelStatistics = new MediaChannelStatistics(mChannelStatistics);
   1.734 +    resource->mChannelStatistics->Stop();
   1.735 +  }
   1.736 +  return resource.forget();
   1.737 +}
   1.738 +
   1.739 +void ChannelMediaResource::CloseChannel()
   1.740 +{
   1.741 +  NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
   1.742 +
   1.743 +  {
   1.744 +    MutexAutoLock lock(mLock);
   1.745 +    mChannelStatistics->Stop();
   1.746 +  }
   1.747 +
   1.748 +  if (mListener) {
   1.749 +    mListener->Revoke();
   1.750 +    mListener = nullptr;
   1.751 +  }
   1.752 +
   1.753 +  if (mChannel) {
   1.754 +    if (mSuspendCount > 0) {
   1.755 +      // Resume the channel before we cancel it
   1.756 +      PossiblyResume();
   1.757 +    }
   1.758 +    // The status we use here won't be passed to the decoder, since
   1.759 +    // we've already revoked the listener. It can however be passed
   1.760 +    // to nsDocumentViewer::LoadComplete if our channel is the one
   1.761 +    // that kicked off creation of a video document. We don't want that
   1.762 +    // document load to think there was an error.
   1.763 +    // NS_ERROR_PARSED_DATA_CACHED is the best thing we have for that
   1.764 +    // at the moment.
   1.765 +    mChannel->Cancel(NS_ERROR_PARSED_DATA_CACHED);
   1.766 +    mChannel = nullptr;
   1.767 +  }
   1.768 +}
   1.769 +
   1.770 +nsresult ChannelMediaResource::ReadFromCache(char* aBuffer,
   1.771 +                                             int64_t aOffset,
   1.772 +                                             uint32_t aCount)
   1.773 +{
   1.774 +  return mCacheStream.ReadFromCache(aBuffer, aOffset, aCount);
   1.775 +}
   1.776 +
   1.777 +nsresult ChannelMediaResource::Read(char* aBuffer,
   1.778 +                                    uint32_t aCount,
   1.779 +                                    uint32_t* aBytes)
   1.780 +{
   1.781 +  NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
   1.782 +
   1.783 +  int64_t offset = mCacheStream.Tell();
   1.784 +  nsresult rv = mCacheStream.Read(aBuffer, aCount, aBytes);
   1.785 +  if (NS_SUCCEEDED(rv)) {
   1.786 +    DispatchBytesConsumed(*aBytes, offset);
   1.787 +  }
   1.788 +  return rv;
   1.789 +}
   1.790 +
   1.791 +nsresult ChannelMediaResource::ReadAt(int64_t aOffset,
   1.792 +                                      char* aBuffer,
   1.793 +                                      uint32_t aCount,
   1.794 +                                      uint32_t* aBytes)
   1.795 +{
   1.796 +  NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
   1.797 +
   1.798 +  nsresult rv = mCacheStream.ReadAt(aOffset, aBuffer, aCount, aBytes);
   1.799 +  if (NS_SUCCEEDED(rv)) {
   1.800 +    DispatchBytesConsumed(*aBytes, aOffset);
   1.801 +  }
   1.802 +  return rv;
   1.803 +}
   1.804 +
   1.805 +nsresult ChannelMediaResource::Seek(int32_t aWhence, int64_t aOffset)
   1.806 +{
   1.807 +  NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
   1.808 +
   1.809 +  CMLOG("Seek requested for aOffset [%lld] for decoder [%p]",
   1.810 +        aOffset, mDecoder);
   1.811 +  return mCacheStream.Seek(aWhence, aOffset);
   1.812 +}
   1.813 +
   1.814 +void ChannelMediaResource::StartSeekingForMetadata()
   1.815 +{
   1.816 +  mSeekingForMetadata = true;
   1.817 +}
   1.818 +
   1.819 +void ChannelMediaResource::EndSeekingForMetadata()
   1.820 +{
   1.821 +  mSeekingForMetadata = false;
   1.822 +}
   1.823 +
   1.824 +int64_t ChannelMediaResource::Tell()
   1.825 +{
   1.826 +  NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
   1.827 +
   1.828 +  return mCacheStream.Tell();
   1.829 +}
   1.830 +
   1.831 +nsresult ChannelMediaResource::GetCachedRanges(nsTArray<MediaByteRange>& aRanges)
   1.832 +{
   1.833 +  return mCacheStream.GetCachedRanges(aRanges);
   1.834 +}
   1.835 +
   1.836 +void ChannelMediaResource::Suspend(bool aCloseImmediately)
   1.837 +{
   1.838 +  NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread");
   1.839 +
   1.840 +  MediaDecoderOwner* owner = mDecoder->GetMediaOwner();
   1.841 +  if (!owner) {
   1.842 +    // Shutting down; do nothing.
   1.843 +    return;
   1.844 +  }
   1.845 +  dom::HTMLMediaElement* element = owner->GetMediaElement();
   1.846 +  if (!element) {
   1.847 +    // Shutting down; do nothing.
   1.848 +    return;
   1.849 +  }
   1.850 +
   1.851 +  if (mChannel) {
   1.852 +    if (aCloseImmediately && mCacheStream.IsTransportSeekable()) {
   1.853 +      // Kill off our channel right now, but don't tell anyone about it.
   1.854 +      mIgnoreClose = true;
   1.855 +      CloseChannel();
   1.856 +      element->DownloadSuspended();
   1.857 +    } else if (mSuspendCount == 0) {
   1.858 +      {
   1.859 +        MutexAutoLock lock(mLock);
   1.860 +        mChannelStatistics->Stop();
   1.861 +      }
   1.862 +      PossiblySuspend();
   1.863 +      element->DownloadSuspended();
   1.864 +    }
   1.865 +  }
   1.866 +
   1.867 +  ++mSuspendCount;
   1.868 +}
   1.869 +
   1.870 +void ChannelMediaResource::Resume()
   1.871 +{
   1.872 +  NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread");
   1.873 +  NS_ASSERTION(mSuspendCount > 0, "Too many resumes!");
   1.874 +
   1.875 +  MediaDecoderOwner* owner = mDecoder->GetMediaOwner();
   1.876 +  if (!owner) {
   1.877 +    // Shutting down; do nothing.
   1.878 +    return;
   1.879 +  }
   1.880 +  dom::HTMLMediaElement* element = owner->GetMediaElement();
   1.881 +  if (!element) {
   1.882 +    // Shutting down; do nothing.
   1.883 +    return;
   1.884 +  }
   1.885 +
   1.886 +  NS_ASSERTION(mSuspendCount > 0, "Resume without previous Suspend!");
   1.887 +  --mSuspendCount;
   1.888 +  if (mSuspendCount == 0) {
   1.889 +    if (mChannel) {
   1.890 +      // Just wake up our existing channel
   1.891 +      {
   1.892 +        MutexAutoLock lock(mLock);
   1.893 +        mChannelStatistics->Start();
   1.894 +      }
   1.895 +      // if an error occurs after Resume, assume it's because the server
   1.896 +      // timed out the connection and we should reopen it.
   1.897 +      mReopenOnError = true;
   1.898 +      PossiblyResume();
   1.899 +      element->DownloadResumed();
   1.900 +    } else {
   1.901 +      int64_t totalLength = mCacheStream.GetLength();
   1.902 +      // If mOffset is at the end of the stream, then we shouldn't try to
   1.903 +      // seek to it. The seek will fail and be wasted anyway. We can leave
   1.904 +      // the channel dead; if the media cache wants to read some other data
   1.905 +      // in the future, it will call CacheClientSeek itself which will reopen the
   1.906 +      // channel.
   1.907 +      if (totalLength < 0 || mOffset < totalLength) {
   1.908 +        // There is (or may be) data to read at mOffset, so start reading it.
   1.909 +        // Need to recreate the channel.
   1.910 +        CacheClientSeek(mOffset, false);
   1.911 +      }
   1.912 +      element->DownloadResumed();
   1.913 +    }
   1.914 +  }
   1.915 +}
   1.916 +
   1.917 +nsresult
   1.918 +ChannelMediaResource::RecreateChannel()
   1.919 +{
   1.920 +  nsLoadFlags loadFlags =
   1.921 +    nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE_IF_BUSY |
   1.922 +    (mLoadInBackground ? nsIRequest::LOAD_BACKGROUND : 0);
   1.923 +
   1.924 +  MediaDecoderOwner* owner = mDecoder->GetMediaOwner();
   1.925 +  if (!owner) {
   1.926 +    // The decoder is being shut down, so don't bother opening a new channel
   1.927 +    return NS_OK;
   1.928 +  }
   1.929 +  dom::HTMLMediaElement* element = owner->GetMediaElement();
   1.930 +  if (!element) {
   1.931 +    // The decoder is being shut down, so don't bother opening a new channel
   1.932 +    return NS_OK;
   1.933 +  }
   1.934 +  nsCOMPtr<nsILoadGroup> loadGroup = element->GetDocumentLoadGroup();
   1.935 +  NS_ENSURE_TRUE(loadGroup, NS_ERROR_NULL_POINTER);
   1.936 +
   1.937 +  nsresult rv = NS_NewChannel(getter_AddRefs(mChannel),
   1.938 +                              mURI,
   1.939 +                              nullptr,
   1.940 +                              loadGroup,
   1.941 +                              nullptr,
   1.942 +                              loadFlags);
   1.943 +
   1.944 +  // We have cached the Content-Type, which should not change. Give a hint to
   1.945 +  // the channel to avoid a sniffing failure, which would be expected because we
   1.946 +  // are probably seeking in the middle of the bitstream, and sniffing relies
   1.947 +  // on the presence of a magic number at the beginning of the stream.
   1.948 +  NS_ASSERTION(!GetContentType().IsEmpty(),
   1.949 +      "When recreating a channel, we should know the Content-Type.");
   1.950 +  mChannel->SetContentType(GetContentType());
   1.951 +
   1.952 +  return rv;
   1.953 +}
   1.954 +
   1.955 +void
   1.956 +ChannelMediaResource::DoNotifyDataReceived()
   1.957 +{
   1.958 +  mDataReceivedEvent.Revoke();
   1.959 +  mDecoder->NotifyBytesDownloaded();
   1.960 +}
   1.961 +
   1.962 +void
   1.963 +ChannelMediaResource::CacheClientNotifyDataReceived()
   1.964 +{
   1.965 +  NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread");
   1.966 +  // NOTE: this can be called with the media cache lock held, so don't
   1.967 +  // block or do anything which might try to acquire a lock!
   1.968 +
   1.969 +  if (mDataReceivedEvent.IsPending())
   1.970 +    return;
   1.971 +
   1.972 +  mDataReceivedEvent =
   1.973 +    NS_NewNonOwningRunnableMethod(this, &ChannelMediaResource::DoNotifyDataReceived);
   1.974 +  NS_DispatchToMainThread(mDataReceivedEvent.get(), NS_DISPATCH_NORMAL);
   1.975 +}
   1.976 +
   1.977 +class DataEnded : public nsRunnable {
   1.978 +public:
   1.979 +  DataEnded(MediaDecoder* aDecoder, nsresult aStatus) :
   1.980 +    mDecoder(aDecoder), mStatus(aStatus) {}
   1.981 +  NS_IMETHOD Run() {
   1.982 +    mDecoder->NotifyDownloadEnded(mStatus);
   1.983 +    return NS_OK;
   1.984 +  }
   1.985 +private:
   1.986 +  nsRefPtr<MediaDecoder> mDecoder;
   1.987 +  nsresult                 mStatus;
   1.988 +};
   1.989 +
   1.990 +void
   1.991 +ChannelMediaResource::CacheClientNotifyDataEnded(nsresult aStatus)
   1.992 +{
   1.993 +  NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread");
   1.994 +  // NOTE: this can be called with the media cache lock held, so don't
   1.995 +  // block or do anything which might try to acquire a lock!
   1.996 +
   1.997 +  nsCOMPtr<nsIRunnable> event = new DataEnded(mDecoder, aStatus);
   1.998 +  NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
   1.999 +}
  1.1000 +
  1.1001 +void
  1.1002 +ChannelMediaResource::CacheClientNotifyPrincipalChanged()
  1.1003 +{
  1.1004 +  NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread");
  1.1005 +
  1.1006 +  mDecoder->NotifyPrincipalChanged();
  1.1007 +}
  1.1008 +
  1.1009 +nsresult
  1.1010 +ChannelMediaResource::CacheClientSeek(int64_t aOffset, bool aResume)
  1.1011 +{
  1.1012 +  NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread");
  1.1013 +
  1.1014 +  CMLOG("CacheClientSeek requested for aOffset [%lld] for decoder [%p]",
  1.1015 +        aOffset, mDecoder);
  1.1016 +
  1.1017 +  CloseChannel();
  1.1018 +
  1.1019 +  if (aResume) {
  1.1020 +    NS_ASSERTION(mSuspendCount > 0, "Too many resumes!");
  1.1021 +    // No need to mess with the channel, since we're making a new one
  1.1022 +    --mSuspendCount;
  1.1023 +  }
  1.1024 +
  1.1025 +  mOffset = aOffset;
  1.1026 +
  1.1027 +  if (mSuspendCount > 0) {
  1.1028 +    // Close the existing channel to force the channel to be recreated at
  1.1029 +    // the correct offset upon resume.
  1.1030 +    if (mChannel) {
  1.1031 +      mIgnoreClose = true;
  1.1032 +      CloseChannel();
  1.1033 +    }
  1.1034 +    return NS_OK;
  1.1035 +  }
  1.1036 +
  1.1037 +  nsresult rv = RecreateChannel();
  1.1038 +  if (NS_FAILED(rv))
  1.1039 +    return rv;
  1.1040 +
  1.1041 +  return OpenChannel(nullptr);
  1.1042 +}
  1.1043 +
  1.1044 +void
  1.1045 +ChannelMediaResource::FlushCache()
  1.1046 +{
  1.1047 +  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
  1.1048 +
  1.1049 +  // Ensure that data in the cache's partial block is written to disk.
  1.1050 +  mCacheStream.FlushPartialBlock();
  1.1051 +}
  1.1052 +
  1.1053 +void
  1.1054 +ChannelMediaResource::NotifyLastByteRange()
  1.1055 +{
  1.1056 +  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
  1.1057 +
  1.1058 +  // Tell media cache that the last data has been downloaded.
  1.1059 +  // Note: subsequent seeks will require re-opening the channel etc.
  1.1060 +  mCacheStream.NotifyDataEnded(NS_OK);
  1.1061 +
  1.1062 +}
  1.1063 +
  1.1064 +nsresult
  1.1065 +ChannelMediaResource::CacheClientSuspend()
  1.1066 +{
  1.1067 +  Suspend(false);
  1.1068 +
  1.1069 +  mDecoder->NotifySuspendedStatusChanged();
  1.1070 +  return NS_OK;
  1.1071 +}
  1.1072 +
  1.1073 +nsresult
  1.1074 +ChannelMediaResource::CacheClientResume()
  1.1075 +{
  1.1076 +  Resume();
  1.1077 +
  1.1078 +  mDecoder->NotifySuspendedStatusChanged();
  1.1079 +  return NS_OK;
  1.1080 +}
  1.1081 +
  1.1082 +int64_t
  1.1083 +ChannelMediaResource::GetNextCachedData(int64_t aOffset)
  1.1084 +{
  1.1085 +  return mCacheStream.GetNextCachedData(aOffset);
  1.1086 +}
  1.1087 +
  1.1088 +int64_t
  1.1089 +ChannelMediaResource::GetCachedDataEnd(int64_t aOffset)
  1.1090 +{
  1.1091 +  return mCacheStream.GetCachedDataEnd(aOffset);
  1.1092 +}
  1.1093 +
  1.1094 +bool
  1.1095 +ChannelMediaResource::IsDataCachedToEndOfResource(int64_t aOffset)
  1.1096 +{
  1.1097 +  return mCacheStream.IsDataCachedToEndOfStream(aOffset);
  1.1098 +}
  1.1099 +
  1.1100 +void
  1.1101 +ChannelMediaResource::EnsureCacheUpToDate()
  1.1102 +{
  1.1103 +  mCacheStream.EnsureCacheUpdate();
  1.1104 +}
  1.1105 +
  1.1106 +bool
  1.1107 +ChannelMediaResource::IsSuspendedByCache()
  1.1108 +{
  1.1109 +  return mCacheStream.AreAllStreamsForResourceSuspended();
  1.1110 +}
  1.1111 +
  1.1112 +bool
  1.1113 +ChannelMediaResource::IsSuspended()
  1.1114 +{
  1.1115 +  MutexAutoLock lock(mLock);
  1.1116 +  return mSuspendCount > 0;
  1.1117 +}
  1.1118 +
  1.1119 +void
  1.1120 +ChannelMediaResource::SetReadMode(MediaCacheStream::ReadMode aMode)
  1.1121 +{
  1.1122 +  mCacheStream.SetReadMode(aMode);
  1.1123 +}
  1.1124 +
  1.1125 +void
  1.1126 +ChannelMediaResource::SetPlaybackRate(uint32_t aBytesPerSecond)
  1.1127 +{
  1.1128 +  mCacheStream.SetPlaybackRate(aBytesPerSecond);
  1.1129 +}
  1.1130 +
  1.1131 +void
  1.1132 +ChannelMediaResource::Pin()
  1.1133 +{
  1.1134 +  mCacheStream.Pin();
  1.1135 +}
  1.1136 +
  1.1137 +void
  1.1138 +ChannelMediaResource::Unpin()
  1.1139 +{
  1.1140 +  mCacheStream.Unpin();
  1.1141 +}
  1.1142 +
  1.1143 +double
  1.1144 +ChannelMediaResource::GetDownloadRate(bool* aIsReliable)
  1.1145 +{
  1.1146 +  MutexAutoLock lock(mLock);
  1.1147 +  return mChannelStatistics->GetRate(aIsReliable);
  1.1148 +}
  1.1149 +
  1.1150 +int64_t
  1.1151 +ChannelMediaResource::GetLength()
  1.1152 +{
  1.1153 +  return mCacheStream.GetLength();
  1.1154 +}
  1.1155 +
  1.1156 +void
  1.1157 +ChannelMediaResource::PossiblySuspend()
  1.1158 +{
  1.1159 +  bool isPending = false;
  1.1160 +  nsresult rv = mChannel->IsPending(&isPending);
  1.1161 +  if (NS_SUCCEEDED(rv) && isPending) {
  1.1162 +    mChannel->Suspend();
  1.1163 +    mIgnoreResume = false;
  1.1164 +  } else {
  1.1165 +    mIgnoreResume = true;
  1.1166 +  }
  1.1167 +}
  1.1168 +
  1.1169 +void
  1.1170 +ChannelMediaResource::PossiblyResume()
  1.1171 +{
  1.1172 +  if (!mIgnoreResume) {
  1.1173 +    mChannel->Resume();
  1.1174 +  } else {
  1.1175 +    mIgnoreResume = false;
  1.1176 +  }
  1.1177 +}
  1.1178 +
  1.1179 +class FileMediaResource : public BaseMediaResource
  1.1180 +{
  1.1181 +public:
  1.1182 +  FileMediaResource(MediaDecoder* aDecoder,
  1.1183 +                    nsIChannel* aChannel,
  1.1184 +                    nsIURI* aURI,
  1.1185 +                    const nsACString& aContentType) :
  1.1186 +    BaseMediaResource(aDecoder, aChannel, aURI, aContentType),
  1.1187 +    mSize(-1),
  1.1188 +    mLock("FileMediaResource.mLock"),
  1.1189 +    mSizeInitialized(false)
  1.1190 +  {
  1.1191 +  }
  1.1192 +  ~FileMediaResource()
  1.1193 +  {
  1.1194 +  }
  1.1195 +
  1.1196 +  // Main thread
  1.1197 +  virtual nsresult Open(nsIStreamListener** aStreamListener);
  1.1198 +  virtual nsresult Close();
  1.1199 +  virtual void     Suspend(bool aCloseImmediately) {}
  1.1200 +  virtual void     Resume() {}
  1.1201 +  virtual already_AddRefed<nsIPrincipal> GetCurrentPrincipal();
  1.1202 +  virtual bool     CanClone();
  1.1203 +  virtual already_AddRefed<MediaResource> CloneData(MediaDecoder* aDecoder);
  1.1204 +  virtual nsresult ReadFromCache(char* aBuffer, int64_t aOffset, uint32_t aCount);
  1.1205 +
  1.1206 +  // These methods are called off the main thread.
  1.1207 +
  1.1208 +  // Other thread
  1.1209 +  virtual void     SetReadMode(MediaCacheStream::ReadMode aMode) {}
  1.1210 +  virtual void     SetPlaybackRate(uint32_t aBytesPerSecond) {}
  1.1211 +  virtual nsresult Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes);
  1.1212 +  virtual nsresult ReadAt(int64_t aOffset, char* aBuffer,
  1.1213 +                          uint32_t aCount, uint32_t* aBytes);
  1.1214 +  virtual nsresult Seek(int32_t aWhence, int64_t aOffset);
  1.1215 +  virtual void     StartSeekingForMetadata() {};
  1.1216 +  virtual void     EndSeekingForMetadata() {};
  1.1217 +  virtual int64_t  Tell();
  1.1218 +
  1.1219 +  // Any thread
  1.1220 +  virtual void    Pin() {}
  1.1221 +  virtual void    Unpin() {}
  1.1222 +  virtual double  GetDownloadRate(bool* aIsReliable)
  1.1223 +  {
  1.1224 +    // The data's all already here
  1.1225 +    *aIsReliable = true;
  1.1226 +    return 100*1024*1024; // arbitray, use 100MB/s
  1.1227 +  }
  1.1228 +  virtual int64_t GetLength() {
  1.1229 +    MutexAutoLock lock(mLock);
  1.1230 +
  1.1231 +    EnsureSizeInitialized();
  1.1232 +    return mSizeInitialized ? mSize : 0;
  1.1233 +  }
  1.1234 +  virtual int64_t GetNextCachedData(int64_t aOffset)
  1.1235 +  {
  1.1236 +    MutexAutoLock lock(mLock);
  1.1237 +
  1.1238 +    EnsureSizeInitialized();
  1.1239 +    return (aOffset < mSize) ? aOffset : -1;
  1.1240 +  }
  1.1241 +  virtual int64_t GetCachedDataEnd(int64_t aOffset) {
  1.1242 +    MutexAutoLock lock(mLock);
  1.1243 +
  1.1244 +    EnsureSizeInitialized();
  1.1245 +    return std::max(aOffset, mSize);
  1.1246 +  }
  1.1247 +  virtual bool    IsDataCachedToEndOfResource(int64_t aOffset) { return true; }
  1.1248 +  virtual bool    IsSuspendedByCache() { return false; }
  1.1249 +  virtual bool    IsSuspended() { return false; }
  1.1250 +  virtual bool    IsTransportSeekable() MOZ_OVERRIDE { return true; }
  1.1251 +
  1.1252 +  nsresult GetCachedRanges(nsTArray<MediaByteRange>& aRanges);
  1.1253 +
  1.1254 +  virtual size_t SizeOfExcludingThis(
  1.1255 +                        MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
  1.1256 +  {
  1.1257 +    // Might be useful to track in the future:
  1.1258 +    // - mInput
  1.1259 +    return BaseMediaResource::SizeOfExcludingThis(aMallocSizeOf);
  1.1260 +  }
  1.1261 +
  1.1262 +  virtual size_t SizeOfIncludingThis(
  1.1263 +                        MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
  1.1264 +  {
  1.1265 +    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
  1.1266 +  }
  1.1267 +
  1.1268 +protected:
  1.1269 +  // These Unsafe variants of Read and Seek perform their operations
  1.1270 +  // without acquiring mLock. The caller must obtain the lock before
  1.1271 +  // calling. The implmentation of Read, Seek and ReadAt obtains the
  1.1272 +  // lock before calling these Unsafe variants to read or seek.
  1.1273 +  nsresult UnsafeRead(char* aBuffer, uint32_t aCount, uint32_t* aBytes);
  1.1274 +  nsresult UnsafeSeek(int32_t aWhence, int64_t aOffset);
  1.1275 +private:
  1.1276 +  // Ensures mSize is initialized, if it can be.
  1.1277 +  // mLock must be held when this is called, and mInput must be non-null.
  1.1278 +  void EnsureSizeInitialized();
  1.1279 +
  1.1280 +  // The file size, or -1 if not known. Immutable after Open().
  1.1281 +  // Can be used from any thread.
  1.1282 +  int64_t mSize;
  1.1283 +
  1.1284 +  // This lock handles synchronisation between calls to Close() and
  1.1285 +  // the Read, Seek, etc calls. Close must not be called while a
  1.1286 +  // Read or Seek is in progress since it resets various internal
  1.1287 +  // values to null.
  1.1288 +  // This lock protects mSeekable, mInput, mSize, and mSizeInitialized.
  1.1289 +  Mutex mLock;
  1.1290 +
  1.1291 +  // Seekable stream interface to file. This can be used from any
  1.1292 +  // thread.
  1.1293 +  nsCOMPtr<nsISeekableStream> mSeekable;
  1.1294 +
  1.1295 +  // Input stream for the media data. This can be used from any
  1.1296 +  // thread.
  1.1297 +  nsCOMPtr<nsIInputStream>  mInput;
  1.1298 +
  1.1299 +  // Whether we've attempted to initialize mSize. Note that mSize can be -1
  1.1300 +  // when mSizeInitialized is true if we tried and failed to get the size
  1.1301 +  // of the file.
  1.1302 +  bool mSizeInitialized;
  1.1303 +};
  1.1304 +
  1.1305 +void FileMediaResource::EnsureSizeInitialized()
  1.1306 +{
  1.1307 +  mLock.AssertCurrentThreadOwns();
  1.1308 +  NS_ASSERTION(mInput, "Must have file input stream");
  1.1309 +  if (mSizeInitialized) {
  1.1310 +    return;
  1.1311 +  }
  1.1312 +  mSizeInitialized = true;
  1.1313 +  // Get the file size and inform the decoder.
  1.1314 +  uint64_t size;
  1.1315 +  nsresult res = mInput->Available(&size);
  1.1316 +  if (NS_SUCCEEDED(res) && size <= INT64_MAX) {
  1.1317 +    mSize = (int64_t)size;
  1.1318 +    nsCOMPtr<nsIRunnable> event = new DataEnded(mDecoder, NS_OK);
  1.1319 +    NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
  1.1320 +  }
  1.1321 +}
  1.1322 +
  1.1323 +nsresult FileMediaResource::GetCachedRanges(nsTArray<MediaByteRange>& aRanges)
  1.1324 +{
  1.1325 +  MutexAutoLock lock(mLock);
  1.1326 +
  1.1327 +  EnsureSizeInitialized();
  1.1328 +  if (mSize == -1) {
  1.1329 +    return NS_ERROR_FAILURE;
  1.1330 +  }
  1.1331 +  aRanges.AppendElement(MediaByteRange(0, mSize));
  1.1332 +  return NS_OK;
  1.1333 +}
  1.1334 +
  1.1335 +nsresult FileMediaResource::Open(nsIStreamListener** aStreamListener)
  1.1336 +{
  1.1337 +  NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
  1.1338 +
  1.1339 +  if (aStreamListener) {
  1.1340 +    *aStreamListener = nullptr;
  1.1341 +  }
  1.1342 +
  1.1343 +  nsresult rv = NS_OK;
  1.1344 +  if (aStreamListener) {
  1.1345 +    // The channel is already open. We need a synchronous stream that
  1.1346 +    // implements nsISeekableStream, so we have to find the underlying
  1.1347 +    // file and reopen it
  1.1348 +    nsCOMPtr<nsIFileChannel> fc(do_QueryInterface(mChannel));
  1.1349 +    if (fc) {
  1.1350 +      nsCOMPtr<nsIFile> file;
  1.1351 +      rv = fc->GetFile(getter_AddRefs(file));
  1.1352 +      NS_ENSURE_SUCCESS(rv, rv);
  1.1353 +
  1.1354 +      rv = NS_NewLocalFileInputStream(getter_AddRefs(mInput), file);
  1.1355 +    } else if (IsBlobURI(mURI)) {
  1.1356 +      rv = NS_GetStreamForBlobURI(mURI, getter_AddRefs(mInput));
  1.1357 +    }
  1.1358 +  } else {
  1.1359 +    // Ensure that we never load a local file from some page on a
  1.1360 +    // web server.
  1.1361 +    MediaDecoderOwner* owner = mDecoder->GetMediaOwner();
  1.1362 +    NS_ENSURE_TRUE(owner, NS_ERROR_FAILURE);
  1.1363 +    dom::HTMLMediaElement* element = owner->GetMediaElement();
  1.1364 +    NS_ENSURE_TRUE(element, NS_ERROR_FAILURE);
  1.1365 +
  1.1366 +    rv = nsContentUtils::GetSecurityManager()->
  1.1367 +           CheckLoadURIWithPrincipal(element->NodePrincipal(),
  1.1368 +                                     mURI,
  1.1369 +                                     nsIScriptSecurityManager::STANDARD);
  1.1370 +    NS_ENSURE_SUCCESS(rv, rv);
  1.1371 +
  1.1372 +    rv = mChannel->Open(getter_AddRefs(mInput));
  1.1373 +  }
  1.1374 +  NS_ENSURE_SUCCESS(rv, rv);
  1.1375 +
  1.1376 +  mSeekable = do_QueryInterface(mInput);
  1.1377 +  if (!mSeekable) {
  1.1378 +    // XXX The file may just be a .url or similar
  1.1379 +    // shortcut that points to a Web site. We need to fix this by
  1.1380 +    // doing an async open and waiting until we locate the real resource,
  1.1381 +    // then using that (if it's still a file!).
  1.1382 +    return NS_ERROR_FAILURE;
  1.1383 +  }
  1.1384 +
  1.1385 +  return NS_OK;
  1.1386 +}
  1.1387 +
  1.1388 +nsresult FileMediaResource::Close()
  1.1389 +{
  1.1390 +  NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
  1.1391 +
  1.1392 +  // Since mChennel is only accessed by main thread, there is no necessary to
  1.1393 +  // take the lock.
  1.1394 +  if (mChannel) {
  1.1395 +    mChannel->Cancel(NS_ERROR_PARSED_DATA_CACHED);
  1.1396 +    mChannel = nullptr;
  1.1397 +  }
  1.1398 +
  1.1399 +  return NS_OK;
  1.1400 +}
  1.1401 +
  1.1402 +already_AddRefed<nsIPrincipal> FileMediaResource::GetCurrentPrincipal()
  1.1403 +{
  1.1404 +  NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
  1.1405 +
  1.1406 +  nsCOMPtr<nsIPrincipal> principal;
  1.1407 +  nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
  1.1408 +  if (!secMan || !mChannel)
  1.1409 +    return nullptr;
  1.1410 +  secMan->GetChannelPrincipal(mChannel, getter_AddRefs(principal));
  1.1411 +  return principal.forget();
  1.1412 +}
  1.1413 +
  1.1414 +bool FileMediaResource::CanClone()
  1.1415 +{
  1.1416 +  return true;
  1.1417 +}
  1.1418 +
  1.1419 +already_AddRefed<MediaResource> FileMediaResource::CloneData(MediaDecoder* aDecoder)
  1.1420 +{
  1.1421 +  NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
  1.1422 +
  1.1423 +  MediaDecoderOwner* owner = mDecoder->GetMediaOwner();
  1.1424 +  if (!owner) {
  1.1425 +    // The decoder is being shut down, so we can't clone
  1.1426 +    return nullptr;
  1.1427 +  }
  1.1428 +  dom::HTMLMediaElement* element = owner->GetMediaElement();
  1.1429 +  if (!element) {
  1.1430 +    // The decoder is being shut down, so we can't clone
  1.1431 +    return nullptr;
  1.1432 +  }
  1.1433 +  nsCOMPtr<nsILoadGroup> loadGroup = element->GetDocumentLoadGroup();
  1.1434 +  NS_ENSURE_TRUE(loadGroup, nullptr);
  1.1435 +
  1.1436 +  nsCOMPtr<nsIChannel> channel;
  1.1437 +  nsresult rv =
  1.1438 +    NS_NewChannel(getter_AddRefs(channel), mURI, nullptr, loadGroup, nullptr, 0);
  1.1439 +  if (NS_FAILED(rv))
  1.1440 +    return nullptr;
  1.1441 +
  1.1442 +  nsRefPtr<MediaResource> resource(new FileMediaResource(aDecoder, channel, mURI, GetContentType()));
  1.1443 +  return resource.forget();
  1.1444 +}
  1.1445 +
  1.1446 +nsresult FileMediaResource::ReadFromCache(char* aBuffer, int64_t aOffset, uint32_t aCount)
  1.1447 +{
  1.1448 +  MutexAutoLock lock(mLock);
  1.1449 +
  1.1450 +  EnsureSizeInitialized();
  1.1451 +  int64_t offset = 0;
  1.1452 +  nsresult res = mSeekable->Tell(&offset);
  1.1453 +  NS_ENSURE_SUCCESS(res,res);
  1.1454 +  res = mSeekable->Seek(nsISeekableStream::NS_SEEK_SET, aOffset);
  1.1455 +  NS_ENSURE_SUCCESS(res,res);
  1.1456 +  uint32_t bytesRead = 0;
  1.1457 +  do {
  1.1458 +    uint32_t x = 0;
  1.1459 +    uint32_t bytesToRead = aCount - bytesRead;
  1.1460 +    res = mInput->Read(aBuffer, bytesToRead, &x);
  1.1461 +    bytesRead += x;
  1.1462 +  } while (bytesRead != aCount && res == NS_OK);
  1.1463 +
  1.1464 +  // Reset read head to original position so we don't disturb any other
  1.1465 +  // reading thread.
  1.1466 +  nsresult seekres = mSeekable->Seek(nsISeekableStream::NS_SEEK_SET, offset);
  1.1467 +
  1.1468 +  // If a read failed in the loop above, we want to return its failure code.
  1.1469 +  NS_ENSURE_SUCCESS(res,res);
  1.1470 +
  1.1471 +  // Else we succeed if the reset-seek succeeds.
  1.1472 +  return seekres;
  1.1473 +}
  1.1474 +
  1.1475 +nsresult FileMediaResource::Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes)
  1.1476 +{
  1.1477 +  nsresult rv;
  1.1478 +  int64_t offset = 0;
  1.1479 +  {
  1.1480 +    MutexAutoLock lock(mLock);
  1.1481 +    mSeekable->Tell(&offset);
  1.1482 +    rv = UnsafeRead(aBuffer, aCount, aBytes);
  1.1483 +  }
  1.1484 +  if (NS_SUCCEEDED(rv)) {
  1.1485 +    DispatchBytesConsumed(*aBytes, offset);
  1.1486 +  }
  1.1487 +  return rv;
  1.1488 +}
  1.1489 +
  1.1490 +nsresult FileMediaResource::UnsafeRead(char* aBuffer, uint32_t aCount, uint32_t* aBytes)
  1.1491 +{
  1.1492 +  EnsureSizeInitialized();
  1.1493 +  return mInput->Read(aBuffer, aCount, aBytes);
  1.1494 +}
  1.1495 +
  1.1496 +nsresult FileMediaResource::ReadAt(int64_t aOffset, char* aBuffer,
  1.1497 +                                   uint32_t aCount, uint32_t* aBytes)
  1.1498 +{
  1.1499 +  NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
  1.1500 +
  1.1501 +  nsresult rv;
  1.1502 +  {
  1.1503 +    MutexAutoLock lock(mLock);
  1.1504 +    rv = UnsafeSeek(nsISeekableStream::NS_SEEK_SET, aOffset);
  1.1505 +    if (NS_FAILED(rv)) return rv;
  1.1506 +    rv = UnsafeRead(aBuffer, aCount, aBytes);
  1.1507 +  }
  1.1508 +  if (NS_SUCCEEDED(rv)) {
  1.1509 +    DispatchBytesConsumed(*aBytes, aOffset);
  1.1510 +  }
  1.1511 +  return rv;
  1.1512 +}
  1.1513 +
  1.1514 +nsresult FileMediaResource::Seek(int32_t aWhence, int64_t aOffset)
  1.1515 +{
  1.1516 +  NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
  1.1517 +
  1.1518 +  MutexAutoLock lock(mLock);
  1.1519 +  return UnsafeSeek(aWhence, aOffset);
  1.1520 +}
  1.1521 +
  1.1522 +nsresult FileMediaResource::UnsafeSeek(int32_t aWhence, int64_t aOffset)
  1.1523 +{
  1.1524 +  NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
  1.1525 +
  1.1526 +  if (!mSeekable)
  1.1527 +    return NS_ERROR_FAILURE;
  1.1528 +  EnsureSizeInitialized();
  1.1529 +  return mSeekable->Seek(aWhence, aOffset);
  1.1530 +}
  1.1531 +
  1.1532 +int64_t FileMediaResource::Tell()
  1.1533 +{
  1.1534 +  NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
  1.1535 +
  1.1536 +  MutexAutoLock lock(mLock);
  1.1537 +  if (!mSeekable)
  1.1538 +    return 0;
  1.1539 +  EnsureSizeInitialized();
  1.1540 +
  1.1541 +  int64_t offset = 0;
  1.1542 +  mSeekable->Tell(&offset);
  1.1543 +  return offset;
  1.1544 +}
  1.1545 +
  1.1546 +already_AddRefed<MediaResource>
  1.1547 +MediaResource::Create(MediaDecoder* aDecoder, nsIChannel* aChannel)
  1.1548 +{
  1.1549 +  NS_ASSERTION(NS_IsMainThread(),
  1.1550 +               "MediaResource::Open called on non-main thread");
  1.1551 +
  1.1552 +  // If the channel was redirected, we want the post-redirect URI;
  1.1553 +  // but if the URI scheme was expanded, say from chrome: to jar:file:,
  1.1554 +  // we want the original URI.
  1.1555 +  nsCOMPtr<nsIURI> uri;
  1.1556 +  nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
  1.1557 +  NS_ENSURE_SUCCESS(rv, nullptr);
  1.1558 +
  1.1559 +  nsAutoCString contentType;
  1.1560 +  aChannel->GetContentType(contentType);
  1.1561 +
  1.1562 +  nsCOMPtr<nsIFileChannel> fc = do_QueryInterface(aChannel);
  1.1563 +  nsRefPtr<MediaResource> resource;
  1.1564 +  if (fc || IsBlobURI(uri)) {
  1.1565 +    resource = new FileMediaResource(aDecoder, aChannel, uri, contentType);
  1.1566 +  } else if (IsRtspURI(uri)) {
  1.1567 +    resource = new RtspMediaResource(aDecoder, aChannel, uri, contentType);
  1.1568 +  } else {
  1.1569 +    resource = new ChannelMediaResource(aDecoder, aChannel, uri, contentType);
  1.1570 +  }
  1.1571 +  return resource.forget();
  1.1572 +}
  1.1573 +
  1.1574 +void BaseMediaResource::MoveLoadsToBackground() {
  1.1575 +  NS_ASSERTION(!mLoadInBackground, "Why are you calling this more than once?");
  1.1576 +  mLoadInBackground = true;
  1.1577 +  if (!mChannel) {
  1.1578 +    // No channel, resource is probably already loaded.
  1.1579 +    return;
  1.1580 +  }
  1.1581 +
  1.1582 +  MediaDecoderOwner* owner = mDecoder->GetMediaOwner();
  1.1583 +  if (!owner) {
  1.1584 +    NS_WARNING("Null owner in MediaResource::MoveLoadsToBackground()");
  1.1585 +    return;
  1.1586 +  }
  1.1587 +  dom::HTMLMediaElement* element = owner->GetMediaElement();
  1.1588 +  if (!element) {
  1.1589 +    NS_WARNING("Null element in MediaResource::MoveLoadsToBackground()");
  1.1590 +    return;
  1.1591 +  }
  1.1592 +
  1.1593 +  bool isPending = false;
  1.1594 +  if (NS_SUCCEEDED(mChannel->IsPending(&isPending)) &&
  1.1595 +      isPending) {
  1.1596 +    nsLoadFlags loadFlags;
  1.1597 +    DebugOnly<nsresult> rv = mChannel->GetLoadFlags(&loadFlags);
  1.1598 +    NS_ASSERTION(NS_SUCCEEDED(rv), "GetLoadFlags() failed!");
  1.1599 +
  1.1600 +    loadFlags |= nsIRequest::LOAD_BACKGROUND;
  1.1601 +    ModifyLoadFlags(loadFlags);
  1.1602 +  }
  1.1603 +}
  1.1604 +
  1.1605 +void BaseMediaResource::ModifyLoadFlags(nsLoadFlags aFlags)
  1.1606 +{
  1.1607 +  nsCOMPtr<nsILoadGroup> loadGroup;
  1.1608 +  DebugOnly<nsresult> rv = mChannel->GetLoadGroup(getter_AddRefs(loadGroup));
  1.1609 +  NS_ASSERTION(NS_SUCCEEDED(rv), "GetLoadGroup() failed!");
  1.1610 +
  1.1611 +  nsresult status;
  1.1612 +  mChannel->GetStatus(&status);
  1.1613 +
  1.1614 +  // Note: if (NS_FAILED(status)), the channel won't be in the load group.
  1.1615 +  if (loadGroup &&
  1.1616 +      NS_SUCCEEDED(status)) {
  1.1617 +    rv = loadGroup->RemoveRequest(mChannel, nullptr, status);
  1.1618 +    NS_ASSERTION(NS_SUCCEEDED(rv), "RemoveRequest() failed!");
  1.1619 +  }
  1.1620 +
  1.1621 +  rv = mChannel->SetLoadFlags(aFlags);
  1.1622 +  NS_ASSERTION(NS_SUCCEEDED(rv), "SetLoadFlags() failed!");
  1.1623 +
  1.1624 +  if (loadGroup &&
  1.1625 +      NS_SUCCEEDED(status)) {
  1.1626 +    rv = loadGroup->AddRequest(mChannel, nullptr);
  1.1627 +    NS_ASSERTION(NS_SUCCEEDED(rv), "AddRequest() failed!");
  1.1628 +  }
  1.1629 +}
  1.1630 +
  1.1631 +class DispatchBytesConsumedEvent : public nsRunnable {
  1.1632 +public:
  1.1633 +  DispatchBytesConsumedEvent(MediaDecoder* aDecoder,
  1.1634 +                             int64_t aNumBytes,
  1.1635 +                             int64_t aOffset)
  1.1636 +    : mDecoder(aDecoder),
  1.1637 +      mNumBytes(aNumBytes),
  1.1638 +      mOffset(aOffset)
  1.1639 +  {
  1.1640 +    MOZ_COUNT_CTOR(DispatchBytesConsumedEvent);
  1.1641 +  }
  1.1642 +
  1.1643 +  ~DispatchBytesConsumedEvent()
  1.1644 +  {
  1.1645 +    MOZ_COUNT_DTOR(DispatchBytesConsumedEvent);
  1.1646 +  }
  1.1647 +
  1.1648 +  NS_IMETHOD Run() {
  1.1649 +    mDecoder->NotifyBytesConsumed(mNumBytes, mOffset);
  1.1650 +    // Drop ref to decoder on main thread, just in case this reference
  1.1651 +    // ends up being the last owning reference somehow.
  1.1652 +    mDecoder = nullptr;
  1.1653 +    return NS_OK;
  1.1654 +  }
  1.1655 +
  1.1656 +  RefPtr<MediaDecoder> mDecoder;
  1.1657 +  int64_t mNumBytes;
  1.1658 +  int64_t mOffset;
  1.1659 +};
  1.1660 +
  1.1661 +void BaseMediaResource::DispatchBytesConsumed(int64_t aNumBytes, int64_t aOffset)
  1.1662 +{
  1.1663 +  if (aNumBytes <= 0) {
  1.1664 +    return;
  1.1665 +  }
  1.1666 +  RefPtr<nsIRunnable> event(new DispatchBytesConsumedEvent(mDecoder, aNumBytes, aOffset));
  1.1667 +  NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
  1.1668 +}
  1.1669 +
  1.1670 +} // namespace mozilla
  1.1671 +

mercurial