content/media/MediaResource.cpp

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rw-r--r--

Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

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

mercurial