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