backout 29799f914cab, Bug 917642 - [Helix] Please update the helix blobs
[gecko.git] / content / media / MediaResource.cpp
blobbbe79ddd158e47cdf648cfccf7b7ec99fb3d35e1
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>
34 #ifdef PR_LOGGING
35 PRLogModuleInfo* gMediaResourceLog;
36 #define LOG(msg, ...) PR_LOG(gMediaResourceLog, PR_LOG_DEBUG, \
37 (msg, ##__VA_ARGS__))
38 // Debug logging macro with object pointer and class name.
39 #define CMLOG(msg, ...) \
40 LOG("%p [ChannelMediaResource]: " msg, this, ##__VA_ARGS__)
41 #else
42 #define LOG(msg, ...)
43 #define CMLOG(msg, ...)
44 #endif
46 static const uint32_t HTTP_OK_CODE = 200;
47 static const uint32_t HTTP_PARTIAL_RESPONSE_CODE = 206;
49 namespace mozilla {
51 ChannelMediaResource::ChannelMediaResource(MediaDecoder* aDecoder,
52 nsIChannel* aChannel,
53 nsIURI* aURI,
54 const nsACString& aContentType)
55 : BaseMediaResource(aDecoder, aChannel, aURI, aContentType),
56 mOffset(0), mSuspendCount(0),
57 mReopenOnError(false), mIgnoreClose(false),
58 mCacheStream(MOZ_THIS_IN_INITIALIZER_LIST()),
59 mLock("ChannelMediaResource.mLock"),
60 mIgnoreResume(false),
61 mSeekingForMetadata(false),
62 #ifdef MOZ_DASH
63 mByteRangeDownloads(false),
64 mByteRangeFirstOpen(true),
65 mSeekOffsetMonitor("media.dashseekmonitor"),
66 mSeekOffset(-1),
67 #endif
68 mIsTransportSeekable(true)
70 #ifdef PR_LOGGING
71 if (!gMediaResourceLog) {
72 gMediaResourceLog = PR_NewLogModule("MediaResource");
74 #endif
77 ChannelMediaResource::~ChannelMediaResource()
79 if (mListener) {
80 // Kill its reference to us since we're going away
81 mListener->Revoke();
85 // ChannelMediaResource::Listener just observes the channel and
86 // forwards notifications to the ChannelMediaResource. We use multiple
87 // listener objects so that when we open a new stream for a seek we can
88 // disconnect the old listener from the ChannelMediaResource and hook up
89 // a new listener, so notifications from the old channel are discarded
90 // and don't confuse us.
91 NS_IMPL_ISUPPORTS4(ChannelMediaResource::Listener,
92 nsIRequestObserver, nsIStreamListener, nsIChannelEventSink,
93 nsIInterfaceRequestor)
95 nsresult
96 ChannelMediaResource::Listener::OnStartRequest(nsIRequest* aRequest,
97 nsISupports* aContext)
99 if (!mResource)
100 return NS_OK;
101 return mResource->OnStartRequest(aRequest);
104 nsresult
105 ChannelMediaResource::Listener::OnStopRequest(nsIRequest* aRequest,
106 nsISupports* aContext,
107 nsresult aStatus)
109 if (!mResource)
110 return NS_OK;
111 return mResource->OnStopRequest(aRequest, aStatus);
114 nsresult
115 ChannelMediaResource::Listener::OnDataAvailable(nsIRequest* aRequest,
116 nsISupports* aContext,
117 nsIInputStream* aStream,
118 uint64_t aOffset,
119 uint32_t aCount)
121 if (!mResource)
122 return NS_OK;
123 return mResource->OnDataAvailable(aRequest, aStream, aCount);
126 nsresult
127 ChannelMediaResource::Listener::AsyncOnChannelRedirect(nsIChannel* aOldChannel,
128 nsIChannel* aNewChannel,
129 uint32_t aFlags,
130 nsIAsyncVerifyRedirectCallback* cb)
132 nsresult rv = NS_OK;
133 if (mResource)
134 rv = mResource->OnChannelRedirect(aOldChannel, aNewChannel, aFlags);
136 if (NS_FAILED(rv))
137 return rv;
139 cb->OnRedirectVerifyCallback(NS_OK);
140 return NS_OK;
143 nsresult
144 ChannelMediaResource::Listener::GetInterface(const nsIID & aIID, void **aResult)
146 return QueryInterface(aIID, aResult);
149 nsresult
150 ChannelMediaResource::OnStartRequest(nsIRequest* aRequest)
152 NS_ASSERTION(mChannel.get() == aRequest, "Wrong channel!");
154 MediaDecoderOwner* owner = mDecoder->GetMediaOwner();
155 NS_ENSURE_TRUE(owner, NS_ERROR_FAILURE);
156 HTMLMediaElement* element = owner->GetMediaElement();
157 NS_ENSURE_TRUE(element, NS_ERROR_FAILURE);
158 nsresult status;
159 nsresult rv = aRequest->GetStatus(&status);
160 NS_ENSURE_SUCCESS(rv, rv);
162 if (element->ShouldCheckAllowOrigin()) {
163 // If the request was cancelled by nsCORSListenerProxy due to failing
164 // the CORS security check, send an error through to the media element.
165 if (status == NS_ERROR_DOM_BAD_URI) {
166 mDecoder->NetworkError();
167 return NS_ERROR_DOM_BAD_URI;
171 nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(aRequest);
172 bool seekable = false;
173 if (hc) {
174 uint32_t responseStatus = 0;
175 hc->GetResponseStatus(&responseStatus);
176 bool succeeded = false;
177 hc->GetRequestSucceeded(&succeeded);
179 if (!succeeded && NS_SUCCEEDED(status)) {
180 // HTTP-level error (e.g. 4xx); treat this as a fatal network-level error.
181 // We might get this on a seek.
182 // (Note that lower-level errors indicated by NS_FAILED(status) are
183 // handled in OnStopRequest.)
184 // A 416 error should treated as EOF here... it's possible
185 // that we don't get Content-Length, we read N bytes, then we
186 // suspend and resume, the resume reopens the channel and we seek to
187 // offset N, but there are no more bytes, so we get a 416
188 // "Requested Range Not Satisfiable".
189 if (responseStatus == HTTP_REQUESTED_RANGE_NOT_SATISFIABLE_CODE) {
190 // OnStopRequest will not be fired, so we need to do some of its
191 // work here.
192 mCacheStream.NotifyDataEnded(status);
193 } else {
194 mDecoder->NetworkError();
197 // This disconnects our listener so we don't get any more data. We
198 // certainly don't want an error page to end up in our cache!
199 CloseChannel();
200 return NS_OK;
203 nsAutoCString ranges;
204 hc->GetResponseHeader(NS_LITERAL_CSTRING("Accept-Ranges"),
205 ranges);
206 bool acceptsRanges = ranges.EqualsLiteral("bytes");
207 // True if this channel will not return an unbounded amount of data
208 bool dataIsBounded = false;
210 int64_t contentLength = -1;
211 hc->GetContentLength(&contentLength);
212 if (contentLength >= 0 && responseStatus == HTTP_OK_CODE) {
213 // "OK" status means Content-Length is for the whole resource.
214 // Since that's bounded, we know we have a finite-length resource.
215 dataIsBounded = true;
218 if (mOffset == 0) {
219 // Look for duration headers from known Ogg content systems.
220 // In the case of multiple options for obtaining the duration
221 // the order of precedence is:
222 // 1) The Media resource metadata if possible (done by the decoder itself).
223 // 2) Content-Duration message header.
224 // 3) X-AMZ-Meta-Content-Duration.
225 // 4) X-Content-Duration.
226 // 5) Perform a seek in the decoder to find the value.
227 nsAutoCString durationText;
228 nsresult ec = NS_OK;
229 rv = hc->GetResponseHeader(NS_LITERAL_CSTRING("Content-Duration"), durationText);
230 if (NS_FAILED(rv)) {
231 rv = hc->GetResponseHeader(NS_LITERAL_CSTRING("X-AMZ-Meta-Content-Duration"), durationText);
233 if (NS_FAILED(rv)) {
234 rv = hc->GetResponseHeader(NS_LITERAL_CSTRING("X-Content-Duration"), durationText);
237 // If there is a Content-Duration header with a valid value, record
238 // the duration.
239 if (NS_SUCCEEDED(rv)) {
240 double duration = durationText.ToDouble(&ec);
241 if (ec == NS_OK && duration >= 0) {
242 mDecoder->SetDuration(duration);
243 // We know the resource must be bounded.
244 dataIsBounded = true;
249 // Assume Range requests have a bounded upper limit unless the
250 // Content-Range header tells us otherwise.
251 bool boundedSeekLimit = true;
252 // Check response code for byte-range requests (seeking, chunk requests).
253 if (!mByteRange.IsNull() && (responseStatus == HTTP_PARTIAL_RESPONSE_CODE)) {
254 // Parse Content-Range header.
255 int64_t rangeStart = 0;
256 int64_t rangeEnd = 0;
257 int64_t rangeTotal = 0;
258 rv = ParseContentRangeHeader(hc, rangeStart, rangeEnd, rangeTotal);
259 if (NS_FAILED(rv)) {
260 // Content-Range header text should be parse-able.
261 CMLOG("Error processing \'Content-Range' for "
262 "HTTP_PARTIAL_RESPONSE_CODE: rv[%x] channel[%p] decoder[%p]",
263 rv, hc.get(), mDecoder);
264 mDecoder->NetworkError();
265 CloseChannel();
266 return NS_OK;
269 // Give some warnings if the ranges are unexpected.
270 // XXX These could be error conditions.
271 NS_WARN_IF_FALSE(mByteRange.mStart == rangeStart,
272 "response range start does not match request");
273 NS_WARN_IF_FALSE(mOffset == rangeStart,
274 "response range start does not match current offset");
275 NS_WARN_IF_FALSE(mByteRange.mEnd == rangeEnd,
276 "response range end does not match request");
277 // Notify media cache about the length and start offset of data received.
278 // Note: If aRangeTotal == -1, then the total bytes is unknown at this stage.
279 // For now, tell the decoder that the stream is infinite.
280 if (rangeTotal == -1) {
281 boundedSeekLimit = false;
282 } else {
283 mCacheStream.NotifyDataLength(rangeTotal);
285 mCacheStream.NotifyDataStarted(rangeStart);
287 mOffset = rangeStart;
288 // We received 'Content-Range', so the server accepts range requests.
289 acceptsRanges = true;
290 } else if (((mOffset > 0) || !mByteRange.IsNull())
291 && (responseStatus == HTTP_OK_CODE)) {
292 // If we get an OK response but we were seeking, or requesting a byte
293 // range, then we have to assume that seeking doesn't work. We also need
294 // to tell the cache that it's getting data for the start of the stream.
295 mCacheStream.NotifyDataStarted(0);
296 mOffset = 0;
298 // The server claimed it supported range requests. It lied.
299 acceptsRanges = false;
300 } else if (mOffset == 0 &&
301 (responseStatus == HTTP_OK_CODE ||
302 responseStatus == HTTP_PARTIAL_RESPONSE_CODE)) {
303 if (contentLength >= 0) {
304 mCacheStream.NotifyDataLength(contentLength);
307 // XXX we probably should examine the Content-Range header in case
308 // the server gave us a range which is not quite what we asked for
310 // If we get an HTTP_OK_CODE response to our byte range request,
311 // and the server isn't sending Accept-Ranges:bytes then we don't
312 // support seeking.
313 seekable =
314 responseStatus == HTTP_PARTIAL_RESPONSE_CODE || acceptsRanges;
315 if (seekable && boundedSeekLimit) {
316 // If range requests are supported, and we did not see an unbounded
317 // upper range limit, we assume the resource is bounded.
318 dataIsBounded = true;
321 mDecoder->SetInfinite(!dataIsBounded);
323 mDecoder->SetTransportSeekable(seekable);
324 mCacheStream.SetTransportSeekable(seekable);
327 MutexAutoLock lock(mLock);
328 mIsTransportSeekable = seekable;
329 mChannelStatistics->Start();
332 mReopenOnError = false;
333 // If we are seeking to get metadata, because we are playing an OGG file,
334 // ignore if the channel gets closed without us suspending it explicitly. We
335 // don't want to tell the element that the download has finished whereas we
336 // just happended to have reached the end of the media while seeking.
337 mIgnoreClose = mSeekingForMetadata;
339 if (mSuspendCount > 0) {
340 // Re-suspend the channel if it needs to be suspended
341 // No need to call PossiblySuspend here since the channel is
342 // definitely in the right state for us in OnStartRequest.
343 mChannel->Suspend();
344 mIgnoreResume = false;
347 // Fires an initial progress event and sets up the stall counter so stall events
348 // fire if no download occurs within the required time frame.
349 mDecoder->Progress(false);
351 return NS_OK;
354 bool
355 ChannelMediaResource::IsTransportSeekable()
357 MutexAutoLock lock(mLock);
358 return mIsTransportSeekable;
361 nsresult
362 ChannelMediaResource::ParseContentRangeHeader(nsIHttpChannel * aHttpChan,
363 int64_t& aRangeStart,
364 int64_t& aRangeEnd,
365 int64_t& aRangeTotal)
367 NS_ENSURE_ARG(aHttpChan);
369 nsAutoCString rangeStr;
370 nsresult rv = aHttpChan->GetResponseHeader(NS_LITERAL_CSTRING("Content-Range"),
371 rangeStr);
372 NS_ENSURE_SUCCESS(rv, rv);
373 NS_ENSURE_FALSE(rangeStr.IsEmpty(), NS_ERROR_ILLEGAL_VALUE);
375 // Parse the range header: e.g. Content-Range: bytes 7000-7999/8000.
376 int32_t spacePos = rangeStr.Find(NS_LITERAL_CSTRING(" "));
377 int32_t dashPos = rangeStr.Find(NS_LITERAL_CSTRING("-"), true, spacePos);
378 int32_t slashPos = rangeStr.Find(NS_LITERAL_CSTRING("/"), true, dashPos);
380 nsAutoCString aRangeStartText;
381 rangeStr.Mid(aRangeStartText, spacePos+1, dashPos-(spacePos+1));
382 aRangeStart = aRangeStartText.ToInteger64(&rv);
383 NS_ENSURE_SUCCESS(rv, rv);
384 NS_ENSURE_TRUE(0 <= aRangeStart, NS_ERROR_ILLEGAL_VALUE);
386 nsAutoCString aRangeEndText;
387 rangeStr.Mid(aRangeEndText, dashPos+1, slashPos-(dashPos+1));
388 aRangeEnd = aRangeEndText.ToInteger64(&rv);
389 NS_ENSURE_SUCCESS(rv, rv);
390 NS_ENSURE_TRUE(aRangeStart < aRangeEnd, NS_ERROR_ILLEGAL_VALUE);
392 nsAutoCString aRangeTotalText;
393 rangeStr.Right(aRangeTotalText, rangeStr.Length()-(slashPos+1));
394 if (aRangeTotalText[0] == '*') {
395 aRangeTotal = -1;
396 } else {
397 aRangeTotal = aRangeTotalText.ToInteger64(&rv);
398 NS_ENSURE_TRUE(aRangeEnd < aRangeTotal, NS_ERROR_ILLEGAL_VALUE);
399 NS_ENSURE_SUCCESS(rv, rv);
402 CMLOG("Received bytes [%lld] to [%lld] of [%lld] for decoder[%p]",
403 aRangeStart, aRangeEnd, aRangeTotal, mDecoder);
405 return NS_OK;
408 nsresult
409 ChannelMediaResource::OnStopRequest(nsIRequest* aRequest, nsresult aStatus)
411 NS_ASSERTION(mChannel.get() == aRequest, "Wrong channel!");
412 NS_ASSERTION(mSuspendCount == 0,
413 "How can OnStopRequest fire while we're suspended?");
416 MutexAutoLock lock(mLock);
417 mChannelStatistics->Stop();
420 #ifdef MOZ_DASH
421 // If we were loading a byte range, notify decoder and return.
422 // Skip this for unterminated byte range requests, e.g. seeking for whole
423 // file downloads.
424 if (mByteRangeDownloads) {
425 mDecoder->NotifyDownloadEnded(aStatus);
426 return NS_OK;
428 #endif
430 // Note that aStatus might have succeeded --- this might be a normal close
431 // --- even in situations where the server cut us off because we were
432 // suspended. So we need to "reopen on error" in that case too. The only
433 // cases where we don't need to reopen are when *we* closed the stream.
434 // But don't reopen if we need to seek and we don't think we can... that would
435 // cause us to just re-read the stream, which would be really bad.
436 if (mReopenOnError &&
437 aStatus != NS_ERROR_PARSED_DATA_CACHED && aStatus != NS_BINDING_ABORTED &&
438 (mOffset == 0 || mCacheStream.IsTransportSeekable())) {
439 // If the stream did close normally, then if the server is seekable we'll
440 // just seek to the end of the resource and get an HTTP 416 error because
441 // there's nothing there, so this isn't bad.
442 nsresult rv = CacheClientSeek(mOffset, false);
443 if (NS_SUCCEEDED(rv))
444 return rv;
445 // If the reopen/reseek fails, just fall through and treat this
446 // error as fatal.
449 if (!mIgnoreClose) {
450 mCacheStream.NotifyDataEnded(aStatus);
452 // Move this request back into the foreground. This is necessary for
453 // requests owned by video documents to ensure the load group fires
454 // OnStopRequest when restoring from session history.
455 nsLoadFlags loadFlags;
456 DebugOnly<nsresult> rv = mChannel->GetLoadFlags(&loadFlags);
457 NS_ASSERTION(NS_SUCCEEDED(rv), "GetLoadFlags() failed!");
459 if (loadFlags & nsIRequest::LOAD_BACKGROUND) {
460 ModifyLoadFlags(loadFlags & ~nsIRequest::LOAD_BACKGROUND);
464 return NS_OK;
467 nsresult
468 ChannelMediaResource::OnChannelRedirect(nsIChannel* aOld, nsIChannel* aNew,
469 uint32_t aFlags)
471 mChannel = aNew;
472 SetupChannelHeaders();
473 return NS_OK;
476 struct CopySegmentClosure {
477 nsCOMPtr<nsIPrincipal> mPrincipal;
478 ChannelMediaResource* mResource;
481 NS_METHOD
482 ChannelMediaResource::CopySegmentToCache(nsIInputStream *aInStream,
483 void *aClosure,
484 const char *aFromSegment,
485 uint32_t aToOffset,
486 uint32_t aCount,
487 uint32_t *aWriteCount)
489 CopySegmentClosure* closure = static_cast<CopySegmentClosure*>(aClosure);
491 closure->mResource->mDecoder->NotifyDataArrived(aFromSegment, aCount, closure->mResource->mOffset);
493 #ifdef MOZ_DASH
494 // For byte range downloads controlled by |DASHDecoder|, there are cases in
495 // which the reader's offset is different enough from the channel offset that
496 // |MediaCache| requests a |CacheClientSeek| to the reader's offset. This
497 // can happen between calls to |CopySegmentToCache|. To avoid copying at
498 // incorrect offsets, ensure |MediaCache| copies to the location that
499 // |ChannelMediaResource| expects.
500 if (closure->mResource->mByteRangeDownloads) {
501 closure->mResource->mCacheStream.NotifyDataStarted(closure->mResource->mOffset);
503 #endif
505 // Keep track of where we're up to.
506 LOG("%p [ChannelMediaResource]: CopySegmentToCache at mOffset [%lld] add "
507 "[%d] bytes for decoder[%p]",
508 closure->mResource, closure->mResource->mOffset, aCount,
509 closure->mResource->mDecoder);
510 closure->mResource->mOffset += aCount;
512 closure->mResource->mCacheStream.NotifyDataReceived(aCount, aFromSegment,
513 closure->mPrincipal);
514 *aWriteCount = aCount;
515 return NS_OK;
518 nsresult
519 ChannelMediaResource::OnDataAvailable(nsIRequest* aRequest,
520 nsIInputStream* aStream,
521 uint32_t aCount)
523 NS_ASSERTION(mChannel.get() == aRequest, "Wrong channel!");
526 MutexAutoLock lock(mLock);
527 mChannelStatistics->AddBytes(aCount);
530 CopySegmentClosure closure;
531 nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
532 if (secMan && mChannel) {
533 secMan->GetChannelPrincipal(mChannel, getter_AddRefs(closure.mPrincipal));
535 closure.mResource = this;
537 uint32_t count = aCount;
538 while (count > 0) {
539 uint32_t read;
540 nsresult rv = aStream->ReadSegments(CopySegmentToCache, &closure, count,
541 &read);
542 if (NS_FAILED(rv))
543 return rv;
544 NS_ASSERTION(read > 0, "Read 0 bytes while data was available?");
545 count -= read;
548 return NS_OK;
551 #ifdef MOZ_DASH
552 /* |OpenByteRange|
553 * For terminated byte range requests, use this function.
554 * Callback is |MediaDecoder|::|NotifyByteRangeDownloaded|().
555 * See |CacheClientSeek| also.
558 nsresult
559 ChannelMediaResource::OpenByteRange(nsIStreamListener** aStreamListener,
560 MediaByteRange const & aByteRange)
562 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
564 mByteRangeDownloads = true;
565 mByteRange = aByteRange;
567 // OpenByteRange may be called multiple times; same URL, different ranges.
568 // For the first call using this URL, forward to Open for some init.
569 if (mByteRangeFirstOpen) {
570 mByteRangeFirstOpen = false;
571 return Open(aStreamListener);
574 // For subsequent calls, ensure channel is recreated with correct byte range.
575 CloseChannel();
577 nsresult rv = RecreateChannel();
578 NS_ENSURE_SUCCESS(rv, rv);
580 return OpenChannel(aStreamListener);
582 #endif
584 nsresult ChannelMediaResource::Open(nsIStreamListener **aStreamListener)
586 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
588 if (!mChannelStatistics) {
589 mChannelStatistics = new MediaChannelStatistics();
592 nsresult rv = mCacheStream.Init();
593 if (NS_FAILED(rv))
594 return rv;
595 NS_ASSERTION(mOffset == 0, "Who set mOffset already?");
597 if (!mChannel) {
598 // When we're a clone, the decoder might ask us to Open even though
599 // we haven't established an mChannel (because we might not need one)
600 NS_ASSERTION(!aStreamListener,
601 "Should have already been given a channel if we're to return a stream listener");
602 return NS_OK;
605 return OpenChannel(aStreamListener);
608 nsresult ChannelMediaResource::OpenChannel(nsIStreamListener** aStreamListener)
610 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
611 NS_ENSURE_TRUE(mChannel, NS_ERROR_NULL_POINTER);
612 NS_ASSERTION(!mListener, "Listener should have been removed by now");
614 if (aStreamListener) {
615 *aStreamListener = nullptr;
618 if (mByteRange.IsNull()) {
619 // We're not making a byte range request, so set the content length,
620 // if it's available as an HTTP header. This ensures that MediaResource
621 // wrapping objects for platform libraries that expect to know
622 // the length of a resource can get it before OnStartRequest() fires.
623 nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(mChannel);
624 if (hc) {
625 int64_t cl = -1;
626 if (NS_SUCCEEDED(hc->GetContentLength(&cl)) && cl != -1) {
627 mCacheStream.NotifyDataLength(cl);
632 mListener = new Listener(this);
633 NS_ENSURE_TRUE(mListener, NS_ERROR_OUT_OF_MEMORY);
635 if (aStreamListener) {
636 *aStreamListener = mListener;
637 NS_ADDREF(*aStreamListener);
638 } else {
639 mChannel->SetNotificationCallbacks(mListener.get());
641 nsCOMPtr<nsIStreamListener> listener = mListener.get();
643 // Ensure that if we're loading cross domain, that the server is sending
644 // an authorizing Access-Control header.
645 MediaDecoderOwner* owner = mDecoder->GetMediaOwner();
646 NS_ENSURE_TRUE(owner, NS_ERROR_FAILURE);
647 HTMLMediaElement* element = owner->GetMediaElement();
648 NS_ENSURE_TRUE(element, NS_ERROR_FAILURE);
649 if (element->ShouldCheckAllowOrigin()) {
650 nsRefPtr<nsCORSListenerProxy> crossSiteListener =
651 new nsCORSListenerProxy(mListener,
652 element->NodePrincipal(),
653 false);
654 nsresult rv = crossSiteListener->Init(mChannel);
655 listener = crossSiteListener;
656 NS_ENSURE_TRUE(crossSiteListener, NS_ERROR_OUT_OF_MEMORY);
657 NS_ENSURE_SUCCESS(rv, rv);
658 } else {
659 nsresult rv = nsContentUtils::GetSecurityManager()->
660 CheckLoadURIWithPrincipal(element->NodePrincipal(),
661 mURI,
662 nsIScriptSecurityManager::STANDARD);
663 NS_ENSURE_SUCCESS(rv, rv);
666 SetupChannelHeaders();
668 nsresult rv = mChannel->AsyncOpen(listener, nullptr);
669 NS_ENSURE_SUCCESS(rv, rv);
670 // Tell the media element that we are fetching data from a channel.
671 element->DownloadResumed(true);
674 return NS_OK;
677 void ChannelMediaResource::SetupChannelHeaders()
679 // Always use a byte range request even if we're reading from the start
680 // of the resource.
681 // This enables us to detect if the stream supports byte range
682 // requests, and therefore seeking, early.
683 nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(mChannel);
684 if (hc) {
685 // Use |mByteRange| for a specific chunk, or |mOffset| if seeking in a
686 // complete file download.
687 nsAutoCString rangeString("bytes=");
688 if (!mByteRange.IsNull()) {
689 rangeString.AppendInt(mByteRange.mStart);
690 mOffset = mByteRange.mStart;
691 } else {
692 rangeString.AppendInt(mOffset);
694 rangeString.Append("-");
695 if (!mByteRange.IsNull()) {
696 rangeString.AppendInt(mByteRange.mEnd);
698 hc->SetRequestHeader(NS_LITERAL_CSTRING("Range"), rangeString, false);
700 // Send Accept header for video and audio types only (Bug 489071)
701 NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread");
702 MediaDecoderOwner* owner = mDecoder->GetMediaOwner();
703 if (!owner) {
704 return;
706 HTMLMediaElement* element = owner->GetMediaElement();
707 if (!element) {
708 return;
710 element->SetRequestHeaders(hc);
711 } else {
712 NS_ASSERTION(mOffset == 0, "Don't know how to seek on this channel type");
716 nsresult ChannelMediaResource::Close()
718 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
720 mCacheStream.Close();
721 CloseChannel();
722 return NS_OK;
725 already_AddRefed<nsIPrincipal> ChannelMediaResource::GetCurrentPrincipal()
727 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
729 nsCOMPtr<nsIPrincipal> principal = mCacheStream.GetCurrentPrincipal();
730 return principal.forget();
733 bool ChannelMediaResource::CanClone()
735 return mCacheStream.IsAvailableForSharing();
738 already_AddRefed<MediaResource> ChannelMediaResource::CloneData(MediaDecoder* aDecoder)
740 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
741 NS_ASSERTION(mCacheStream.IsAvailableForSharing(), "Stream can't be cloned");
743 nsRefPtr<ChannelMediaResource> resource =
744 new ChannelMediaResource(aDecoder,
745 nullptr,
746 mURI,
747 GetContentType());
748 if (resource) {
749 // Initially the clone is treated as suspended by the cache, because
750 // we don't have a channel. If the cache needs to read data from the clone
751 // it will call CacheClientResume (or CacheClientSeek with aResume true)
752 // which will recreate the channel. This way, if all of the media data
753 // is already in the cache we don't create an unnecessary HTTP channel
754 // and perform a useless HTTP transaction.
755 resource->mSuspendCount = 1;
756 resource->mCacheStream.InitAsClone(&mCacheStream);
757 resource->mChannelStatistics = new MediaChannelStatistics(mChannelStatistics);
758 resource->mChannelStatistics->Stop();
760 return resource.forget();
763 void ChannelMediaResource::CloseChannel()
765 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
768 MutexAutoLock lock(mLock);
769 mChannelStatistics->Stop();
772 if (mListener) {
773 mListener->Revoke();
774 mListener = nullptr;
777 if (mChannel) {
778 if (mSuspendCount > 0) {
779 // Resume the channel before we cancel it
780 PossiblyResume();
782 // The status we use here won't be passed to the decoder, since
783 // we've already revoked the listener. It can however be passed
784 // to nsDocumentViewer::LoadComplete if our channel is the one
785 // that kicked off creation of a video document. We don't want that
786 // document load to think there was an error.
787 // NS_ERROR_PARSED_DATA_CACHED is the best thing we have for that
788 // at the moment.
789 mChannel->Cancel(NS_ERROR_PARSED_DATA_CACHED);
790 mChannel = nullptr;
794 nsresult ChannelMediaResource::ReadFromCache(char* aBuffer,
795 int64_t aOffset,
796 uint32_t aCount)
798 return mCacheStream.ReadFromCache(aBuffer, aOffset, aCount);
801 nsresult ChannelMediaResource::Read(char* aBuffer,
802 uint32_t aCount,
803 uint32_t* aBytes)
805 NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
807 int64_t offset = mCacheStream.Tell();
808 nsresult rv = mCacheStream.Read(aBuffer, aCount, aBytes);
809 if (NS_SUCCEEDED(rv)) {
810 DispatchBytesConsumed(*aBytes, offset);
812 return rv;
815 nsresult ChannelMediaResource::ReadAt(int64_t aOffset,
816 char* aBuffer,
817 uint32_t aCount,
818 uint32_t* aBytes)
820 NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
822 nsresult rv = mCacheStream.ReadAt(aOffset, aBuffer, aCount, aBytes);
823 if (NS_SUCCEEDED(rv)) {
824 DispatchBytesConsumed(*aBytes, aOffset);
826 return rv;
829 nsresult ChannelMediaResource::Seek(int32_t aWhence, int64_t aOffset)
831 NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
833 CMLOG("Seek requested for aOffset [%lld] for decoder [%p]",
834 aOffset, mDecoder);
835 #ifdef MOZ_DASH
836 // Remember |aOffset|, because Media Cache may request a diff offset later.
837 if (mByteRangeDownloads) {
838 ReentrantMonitorAutoEnter mon(mSeekOffsetMonitor);
839 mSeekOffset = aOffset;
841 #endif
842 return mCacheStream.Seek(aWhence, aOffset);
845 void ChannelMediaResource::StartSeekingForMetadata()
847 mSeekingForMetadata = true;
850 void ChannelMediaResource::EndSeekingForMetadata()
852 mSeekingForMetadata = false;
855 int64_t ChannelMediaResource::Tell()
857 NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
859 return mCacheStream.Tell();
862 nsresult ChannelMediaResource::GetCachedRanges(nsTArray<MediaByteRange>& aRanges)
864 return mCacheStream.GetCachedRanges(aRanges);
867 void ChannelMediaResource::Suspend(bool aCloseImmediately)
869 NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread");
871 MediaDecoderOwner* owner = mDecoder->GetMediaOwner();
872 if (!owner) {
873 // Shutting down; do nothing.
874 return;
876 HTMLMediaElement* element = owner->GetMediaElement();
877 if (!element) {
878 // Shutting down; do nothing.
879 return;
882 if (mChannel) {
883 if (aCloseImmediately && mCacheStream.IsTransportSeekable()) {
884 // Kill off our channel right now, but don't tell anyone about it.
885 mIgnoreClose = true;
886 CloseChannel();
887 element->DownloadSuspended();
888 } else if (mSuspendCount == 0) {
890 MutexAutoLock lock(mLock);
891 mChannelStatistics->Stop();
893 PossiblySuspend();
894 element->DownloadSuspended();
898 ++mSuspendCount;
901 void ChannelMediaResource::Resume()
903 NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread");
904 NS_ASSERTION(mSuspendCount > 0, "Too many resumes!");
906 MediaDecoderOwner* owner = mDecoder->GetMediaOwner();
907 if (!owner) {
908 // Shutting down; do nothing.
909 return;
911 HTMLMediaElement* element = owner->GetMediaElement();
912 if (!element) {
913 // Shutting down; do nothing.
914 return;
917 NS_ASSERTION(mSuspendCount > 0, "Resume without previous Suspend!");
918 --mSuspendCount;
919 if (mSuspendCount == 0) {
920 if (mChannel) {
921 // Just wake up our existing channel
923 MutexAutoLock lock(mLock);
924 mChannelStatistics->Start();
926 // if an error occurs after Resume, assume it's because the server
927 // timed out the connection and we should reopen it.
928 mReopenOnError = true;
929 PossiblyResume();
930 element->DownloadResumed();
931 } else {
932 int64_t totalLength = mCacheStream.GetLength();
933 // If mOffset is at the end of the stream, then we shouldn't try to
934 // seek to it. The seek will fail and be wasted anyway. We can leave
935 // the channel dead; if the media cache wants to read some other data
936 // in the future, it will call CacheClientSeek itself which will reopen the
937 // channel.
938 if (totalLength < 0 || mOffset < totalLength) {
939 // There is (or may be) data to read at mOffset, so start reading it.
940 // Need to recreate the channel.
941 CacheClientSeek(mOffset, false);
943 element->DownloadResumed();
948 nsresult
949 ChannelMediaResource::RecreateChannel()
951 nsLoadFlags loadFlags =
952 nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE_IF_BUSY |
953 (mLoadInBackground ? nsIRequest::LOAD_BACKGROUND : 0);
955 MediaDecoderOwner* owner = mDecoder->GetMediaOwner();
956 if (!owner) {
957 // The decoder is being shut down, so don't bother opening a new channel
958 return NS_OK;
960 HTMLMediaElement* element = owner->GetMediaElement();
961 if (!element) {
962 // The decoder is being shut down, so don't bother opening a new channel
963 return NS_OK;
965 nsCOMPtr<nsILoadGroup> loadGroup = element->GetDocumentLoadGroup();
966 NS_ENSURE_TRUE(loadGroup, NS_ERROR_NULL_POINTER);
968 nsresult rv = NS_NewChannel(getter_AddRefs(mChannel),
969 mURI,
970 nullptr,
971 loadGroup,
972 nullptr,
973 loadFlags);
975 // We have cached the Content-Type, which should not change. Give a hint to
976 // the channel to avoid a sniffing failure, which would be expected because we
977 // are probably seeking in the middle of the bitstream, and sniffing relies
978 // on the presence of a magic number at the beginning of the stream.
979 NS_ASSERTION(!GetContentType().IsEmpty(),
980 "When recreating a channel, we should know the Content-Type.");
981 mChannel->SetContentType(GetContentType());
983 return rv;
986 void
987 ChannelMediaResource::DoNotifyDataReceived()
989 mDataReceivedEvent.Revoke();
990 mDecoder->NotifyBytesDownloaded();
993 void
994 ChannelMediaResource::CacheClientNotifyDataReceived()
996 NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread");
997 // NOTE: this can be called with the media cache lock held, so don't
998 // block or do anything which might try to acquire a lock!
1000 if (mDataReceivedEvent.IsPending())
1001 return;
1003 mDataReceivedEvent =
1004 NS_NewNonOwningRunnableMethod(this, &ChannelMediaResource::DoNotifyDataReceived);
1005 NS_DispatchToMainThread(mDataReceivedEvent.get(), NS_DISPATCH_NORMAL);
1008 class DataEnded : public nsRunnable {
1009 public:
1010 DataEnded(MediaDecoder* aDecoder, nsresult aStatus) :
1011 mDecoder(aDecoder), mStatus(aStatus) {}
1012 NS_IMETHOD Run() {
1013 mDecoder->NotifyDownloadEnded(mStatus);
1014 return NS_OK;
1016 private:
1017 nsRefPtr<MediaDecoder> mDecoder;
1018 nsresult mStatus;
1021 void
1022 ChannelMediaResource::CacheClientNotifyDataEnded(nsresult aStatus)
1024 NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread");
1025 // NOTE: this can be called with the media cache lock held, so don't
1026 // block or do anything which might try to acquire a lock!
1028 nsCOMPtr<nsIRunnable> event = new DataEnded(mDecoder, aStatus);
1029 NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
1032 void
1033 ChannelMediaResource::CacheClientNotifyPrincipalChanged()
1035 NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread");
1037 mDecoder->NotifyPrincipalChanged();
1040 nsresult
1041 ChannelMediaResource::CacheClientSeek(int64_t aOffset, bool aResume)
1043 NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread");
1045 CMLOG("CacheClientSeek requested for aOffset [%lld] for decoder [%p]",
1046 aOffset, mDecoder);
1048 #ifndef MOZ_DASH
1049 CloseChannel();
1050 #else
1051 // |CloseChannel| immediately for non-byte-range downloads.
1052 if (!mByteRangeDownloads) {
1053 CloseChannel();
1054 } else if (mChannel) {
1055 // Only close byte range channels if they are not in pending state.
1056 bool isPending = false;
1057 nsresult rv = mChannel->IsPending(&isPending);
1058 NS_ENSURE_SUCCESS(rv, rv);
1059 if (!isPending) {
1060 CloseChannel();
1063 #endif
1065 if (aResume) {
1066 NS_ASSERTION(mSuspendCount > 0, "Too many resumes!");
1067 // No need to mess with the channel, since we're making a new one
1068 --mSuspendCount;
1071 #ifdef MOZ_DASH // Note: For chunked downloads, e.g. DASH, we need to determine which chunk
1072 // contains the requested offset, |mOffset|. This is either previously
1073 // requested in |Seek| or updated to the most recent bytes downloaded.
1074 // So the process below is:
1075 // 1 - Query decoder for chunk containing desired offset, |mOffset|.
1076 // Return silently if the offset is not available; suggests decoder is
1077 // yet to get range information.
1078 // Return with NetworkError for all other errors.
1080 // 2 - Adjust |mByteRange|.mStart to |aOffset|, requested by media cache.
1081 // For seeking, the media cache always requests the start of the cache
1082 // block, so we need to adjust the first chunk of a seek.
1083 // E.g. For "DASH-WebM On Demand" this means the first chunk after
1084 // seeking will most likely be larger than the subsegment (cluster).
1086 // 3 - Call |OpenByteRange| requesting |mByteRange| bytes.
1088 if (mByteRangeDownloads) {
1089 // Query decoder for chunk containing desired offset.
1090 nsresult rv;
1092 ReentrantMonitorAutoEnter mon(mSeekOffsetMonitor);
1093 // Only continue with seek request if a prior call to |Seek| was made.
1094 // If |Seek| was not called previously, it means the media cache is
1095 // seeking on its own.
1096 // E.g. For those WebM files which are encoded with cues at the end of
1097 // the file, when the cues are parsed, the reader and media cache
1098 // automatically return to the first offset not downloaded, normally the
1099 // first byte after init data. This results in |MediaCache| requesting
1100 // |aOffset| = 0 (aligning to the start of the cache block. Ignore this
1101 // and let |DASHDecoder| decide which bytes to download and when.
1102 if (mSeekOffset >= 0) {
1103 rv = mDecoder->GetByteRangeForSeek(mSeekOffset, mByteRange);
1104 // Cache may try to seek from the next uncached byte: this offset may
1105 // be after the byte range being seeked, i.e. the range containing
1106 // |mSeekOffset|, which is the offset actually requested by the reader.
1107 // This case means that the seeked range is already cached. For byte
1108 // range downloads, we do not permit the cache to request bytes outside
1109 // the seeked range. Instead, the decoder is responsible for
1110 // controlling the sequence of byte range downloads. As such, return
1111 // silently, and do NOT request a new download.
1112 if (NS_SUCCEEDED(rv) && !mByteRange.IsNull() &&
1113 aOffset > mByteRange.mEnd) {
1114 rv = NS_ERROR_NOT_AVAILABLE;
1115 mByteRange.Clear();
1117 mSeekOffset = -1;
1118 } else if (mByteRange.mStart <= aOffset && aOffset <= mByteRange.mEnd) {
1119 CMLOG("Trying to resume download at offset [%lld].", aOffset);
1120 rv = NS_OK;
1121 } else {
1122 CMLOG("MediaCache [%p] trying to seek independently to offset [%lld].",
1123 &mCacheStream, aOffset);
1124 rv = NS_ERROR_NOT_AVAILABLE;
1127 if (rv == NS_ERROR_NOT_AVAILABLE) {
1128 // Decoder will not make byte ranges available for non-active streams, or
1129 // if range information is not yet available, or for metadata bytes if
1130 // they have already been downloaded and read. In all cases, it is ok to
1131 // return silently and assume that the decoder will request the correct
1132 // byte range when range information becomes available.
1133 CMLOG("Byte range not available for decoder [%p]; returning "
1134 "silently.", mDecoder);
1135 return NS_OK;
1136 } else if (NS_FAILED(rv) || mByteRange.IsNull()) {
1137 // Decoder reported an error we don't want to handle here; just return.
1138 CMLOG("Error getting byte range: seek offset[%lld] cache offset[%lld] "
1139 "decoder[%p]", mSeekOffset, aOffset, mDecoder);
1140 mDecoder->NetworkError();
1141 CloseChannel();
1142 return rv;
1144 // Adjust the byte range to start where the media cache requested.
1145 mByteRange.mStart = mOffset = aOffset;
1146 return OpenByteRange(nullptr, mByteRange);
1148 #endif
1150 mOffset = aOffset;
1152 if (mSuspendCount > 0) {
1153 // Close the existing channel to force the channel to be recreated at
1154 // the correct offset upon resume.
1155 if (mChannel) {
1156 mIgnoreClose = true;
1157 CloseChannel();
1159 return NS_OK;
1162 nsresult rv = RecreateChannel();
1163 if (NS_FAILED(rv))
1164 return rv;
1166 return OpenChannel(nullptr);
1169 void
1170 ChannelMediaResource::FlushCache()
1172 NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
1174 // Ensure that data in the cache's partial block is written to disk.
1175 mCacheStream.FlushPartialBlock();
1178 void
1179 ChannelMediaResource::NotifyLastByteRange()
1181 NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
1183 // Tell media cache that the last data has been downloaded.
1184 // Note: subsequent seeks will require re-opening the channel etc.
1185 mCacheStream.NotifyDataEnded(NS_OK);
1189 nsresult
1190 ChannelMediaResource::CacheClientSuspend()
1192 Suspend(false);
1194 mDecoder->NotifySuspendedStatusChanged();
1195 return NS_OK;
1198 nsresult
1199 ChannelMediaResource::CacheClientResume()
1201 Resume();
1203 mDecoder->NotifySuspendedStatusChanged();
1204 return NS_OK;
1207 int64_t
1208 ChannelMediaResource::GetNextCachedData(int64_t aOffset)
1210 return mCacheStream.GetNextCachedData(aOffset);
1213 int64_t
1214 ChannelMediaResource::GetCachedDataEnd(int64_t aOffset)
1216 return mCacheStream.GetCachedDataEnd(aOffset);
1219 bool
1220 ChannelMediaResource::IsDataCachedToEndOfResource(int64_t aOffset)
1222 return mCacheStream.IsDataCachedToEndOfStream(aOffset);
1225 void
1226 ChannelMediaResource::EnsureCacheUpToDate()
1228 mCacheStream.EnsureCacheUpdate();
1231 bool
1232 ChannelMediaResource::IsSuspendedByCache()
1234 return mCacheStream.AreAllStreamsForResourceSuspended();
1237 bool
1238 ChannelMediaResource::IsSuspended()
1240 MutexAutoLock lock(mLock);
1241 return mSuspendCount > 0;
1244 void
1245 ChannelMediaResource::SetReadMode(MediaCacheStream::ReadMode aMode)
1247 mCacheStream.SetReadMode(aMode);
1250 void
1251 ChannelMediaResource::SetPlaybackRate(uint32_t aBytesPerSecond)
1253 mCacheStream.SetPlaybackRate(aBytesPerSecond);
1256 void
1257 ChannelMediaResource::Pin()
1259 mCacheStream.Pin();
1262 void
1263 ChannelMediaResource::Unpin()
1265 mCacheStream.Unpin();
1268 double
1269 ChannelMediaResource::GetDownloadRate(bool* aIsReliable)
1271 MutexAutoLock lock(mLock);
1272 return mChannelStatistics->GetRate(aIsReliable);
1275 int64_t
1276 ChannelMediaResource::GetLength()
1278 return mCacheStream.GetLength();
1281 void
1282 ChannelMediaResource::PossiblySuspend()
1284 bool isPending = false;
1285 nsresult rv = mChannel->IsPending(&isPending);
1286 if (NS_SUCCEEDED(rv) && isPending) {
1287 mChannel->Suspend();
1288 mIgnoreResume = false;
1289 } else {
1290 mIgnoreResume = true;
1294 void
1295 ChannelMediaResource::PossiblyResume()
1297 if (!mIgnoreResume) {
1298 mChannel->Resume();
1299 } else {
1300 mIgnoreResume = false;
1304 class FileMediaResource : public BaseMediaResource
1306 public:
1307 FileMediaResource(MediaDecoder* aDecoder,
1308 nsIChannel* aChannel,
1309 nsIURI* aURI,
1310 const nsACString& aContentType) :
1311 BaseMediaResource(aDecoder, aChannel, aURI, aContentType),
1312 mSize(-1),
1313 mLock("FileMediaResource.mLock"),
1314 mSizeInitialized(false)
1317 ~FileMediaResource()
1321 // Main thread
1322 virtual nsresult Open(nsIStreamListener** aStreamListener);
1323 virtual nsresult Close();
1324 virtual void Suspend(bool aCloseImmediately) {}
1325 virtual void Resume() {}
1326 virtual already_AddRefed<nsIPrincipal> GetCurrentPrincipal();
1327 virtual bool CanClone();
1328 virtual already_AddRefed<MediaResource> CloneData(MediaDecoder* aDecoder);
1329 virtual nsresult ReadFromCache(char* aBuffer, int64_t aOffset, uint32_t aCount);
1331 // These methods are called off the main thread.
1333 // Other thread
1334 virtual void SetReadMode(MediaCacheStream::ReadMode aMode) {}
1335 virtual void SetPlaybackRate(uint32_t aBytesPerSecond) {}
1336 virtual nsresult Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes);
1337 virtual nsresult ReadAt(int64_t aOffset, char* aBuffer,
1338 uint32_t aCount, uint32_t* aBytes);
1339 virtual nsresult Seek(int32_t aWhence, int64_t aOffset);
1340 virtual void StartSeekingForMetadata() {};
1341 virtual void EndSeekingForMetadata() {};
1342 virtual int64_t Tell();
1344 // Any thread
1345 virtual void Pin() {}
1346 virtual void Unpin() {}
1347 virtual double GetDownloadRate(bool* aIsReliable)
1349 // The data's all already here
1350 *aIsReliable = true;
1351 return 100*1024*1024; // arbitray, use 100MB/s
1353 virtual int64_t GetLength() {
1354 MutexAutoLock lock(mLock);
1356 EnsureSizeInitialized();
1357 return mSizeInitialized ? mSize : 0;
1359 virtual int64_t GetNextCachedData(int64_t aOffset)
1361 MutexAutoLock lock(mLock);
1363 EnsureSizeInitialized();
1364 return (aOffset < mSize) ? aOffset : -1;
1366 virtual int64_t GetCachedDataEnd(int64_t aOffset) {
1367 MutexAutoLock lock(mLock);
1369 EnsureSizeInitialized();
1370 return std::max(aOffset, mSize);
1372 virtual bool IsDataCachedToEndOfResource(int64_t aOffset) { return true; }
1373 virtual bool IsSuspendedByCache() { return false; }
1374 virtual bool IsSuspended() { return false; }
1375 virtual bool IsTransportSeekable() MOZ_OVERRIDE { return true; }
1377 nsresult GetCachedRanges(nsTArray<MediaByteRange>& aRanges);
1379 protected:
1380 // These Unsafe variants of Read and Seek perform their operations
1381 // without acquiring mLock. The caller must obtain the lock before
1382 // calling. The implmentation of Read, Seek and ReadAt obtains the
1383 // lock before calling these Unsafe variants to read or seek.
1384 nsresult UnsafeRead(char* aBuffer, uint32_t aCount, uint32_t* aBytes);
1385 nsresult UnsafeSeek(int32_t aWhence, int64_t aOffset);
1386 private:
1387 // Ensures mSize is initialized, if it can be.
1388 // mLock must be held when this is called, and mInput must be non-null.
1389 void EnsureSizeInitialized();
1391 // The file size, or -1 if not known. Immutable after Open().
1392 // Can be used from any thread.
1393 int64_t mSize;
1395 // This lock handles synchronisation between calls to Close() and
1396 // the Read, Seek, etc calls. Close must not be called while a
1397 // Read or Seek is in progress since it resets various internal
1398 // values to null.
1399 // This lock protects mSeekable, mInput, mSize, and mSizeInitialized.
1400 Mutex mLock;
1402 // Seekable stream interface to file. This can be used from any
1403 // thread.
1404 nsCOMPtr<nsISeekableStream> mSeekable;
1406 // Input stream for the media data. This can be used from any
1407 // thread.
1408 nsCOMPtr<nsIInputStream> mInput;
1410 // Whether we've attempted to initialize mSize. Note that mSize can be -1
1411 // when mSizeInitialized is true if we tried and failed to get the size
1412 // of the file.
1413 bool mSizeInitialized;
1416 class LoadedEvent : public nsRunnable
1418 public:
1419 LoadedEvent(MediaDecoder* aDecoder) :
1420 mDecoder(aDecoder)
1422 MOZ_COUNT_CTOR(LoadedEvent);
1424 ~LoadedEvent()
1426 MOZ_COUNT_DTOR(LoadedEvent);
1429 NS_IMETHOD Run() {
1430 mDecoder->NotifyDownloadEnded(NS_OK);
1431 return NS_OK;
1434 private:
1435 nsRefPtr<MediaDecoder> mDecoder;
1438 void FileMediaResource::EnsureSizeInitialized()
1440 mLock.AssertCurrentThreadOwns();
1441 NS_ASSERTION(mInput, "Must have file input stream");
1442 if (mSizeInitialized) {
1443 return;
1445 mSizeInitialized = true;
1446 // Get the file size and inform the decoder.
1447 uint64_t size;
1448 nsresult res = mInput->Available(&size);
1449 if (NS_SUCCEEDED(res) && size <= INT64_MAX) {
1450 mSize = (int64_t)size;
1451 nsCOMPtr<nsIRunnable> event = new LoadedEvent(mDecoder);
1452 NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
1456 nsresult FileMediaResource::GetCachedRanges(nsTArray<MediaByteRange>& aRanges)
1458 MutexAutoLock lock(mLock);
1460 EnsureSizeInitialized();
1461 if (mSize == -1) {
1462 return NS_ERROR_FAILURE;
1464 aRanges.AppendElement(MediaByteRange(0, mSize));
1465 return NS_OK;
1468 nsresult FileMediaResource::Open(nsIStreamListener** aStreamListener)
1470 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
1472 if (aStreamListener) {
1473 *aStreamListener = nullptr;
1476 nsresult rv = NS_OK;
1477 if (aStreamListener) {
1478 // The channel is already open. We need a synchronous stream that
1479 // implements nsISeekableStream, so we have to find the underlying
1480 // file and reopen it
1481 nsCOMPtr<nsIFileChannel> fc(do_QueryInterface(mChannel));
1482 if (fc) {
1483 nsCOMPtr<nsIFile> file;
1484 rv = fc->GetFile(getter_AddRefs(file));
1485 NS_ENSURE_SUCCESS(rv, rv);
1487 rv = NS_NewLocalFileInputStream(getter_AddRefs(mInput), file);
1488 } else if (IsBlobURI(mURI)) {
1489 rv = NS_GetStreamForBlobURI(mURI, getter_AddRefs(mInput));
1491 } else {
1492 // Ensure that we never load a local file from some page on a
1493 // web server.
1494 MediaDecoderOwner* owner = mDecoder->GetMediaOwner();
1495 NS_ENSURE_TRUE(owner, NS_ERROR_FAILURE);
1496 HTMLMediaElement* element = owner->GetMediaElement();
1497 NS_ENSURE_TRUE(element, NS_ERROR_FAILURE);
1499 rv = nsContentUtils::GetSecurityManager()->
1500 CheckLoadURIWithPrincipal(element->NodePrincipal(),
1501 mURI,
1502 nsIScriptSecurityManager::STANDARD);
1503 NS_ENSURE_SUCCESS(rv, rv);
1505 rv = mChannel->Open(getter_AddRefs(mInput));
1507 NS_ENSURE_SUCCESS(rv, rv);
1509 mSeekable = do_QueryInterface(mInput);
1510 if (!mSeekable) {
1511 // XXX The file may just be a .url or similar
1512 // shortcut that points to a Web site. We need to fix this by
1513 // doing an async open and waiting until we locate the real resource,
1514 // then using that (if it's still a file!).
1515 return NS_ERROR_FAILURE;
1518 return NS_OK;
1521 nsresult FileMediaResource::Close()
1523 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
1525 // Since mChennel is only accessed by main thread, there is no necessary to
1526 // take the lock.
1527 if (mChannel) {
1528 mChannel->Cancel(NS_ERROR_PARSED_DATA_CACHED);
1529 mChannel = nullptr;
1532 return NS_OK;
1535 already_AddRefed<nsIPrincipal> FileMediaResource::GetCurrentPrincipal()
1537 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
1539 nsCOMPtr<nsIPrincipal> principal;
1540 nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
1541 if (!secMan || !mChannel)
1542 return nullptr;
1543 secMan->GetChannelPrincipal(mChannel, getter_AddRefs(principal));
1544 return principal.forget();
1547 bool FileMediaResource::CanClone()
1549 return true;
1552 already_AddRefed<MediaResource> FileMediaResource::CloneData(MediaDecoder* aDecoder)
1554 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
1556 MediaDecoderOwner* owner = mDecoder->GetMediaOwner();
1557 if (!owner) {
1558 // The decoder is being shut down, so we can't clone
1559 return nullptr;
1561 HTMLMediaElement* element = owner->GetMediaElement();
1562 if (!element) {
1563 // The decoder is being shut down, so we can't clone
1564 return nullptr;
1566 nsCOMPtr<nsILoadGroup> loadGroup = element->GetDocumentLoadGroup();
1567 NS_ENSURE_TRUE(loadGroup, nullptr);
1569 nsCOMPtr<nsIChannel> channel;
1570 nsresult rv =
1571 NS_NewChannel(getter_AddRefs(channel), mURI, nullptr, loadGroup, nullptr, 0);
1572 if (NS_FAILED(rv))
1573 return nullptr;
1575 nsRefPtr<MediaResource> resource(new FileMediaResource(aDecoder, channel, mURI, GetContentType()));
1576 return resource.forget();
1579 nsresult FileMediaResource::ReadFromCache(char* aBuffer, int64_t aOffset, uint32_t aCount)
1581 MutexAutoLock lock(mLock);
1583 EnsureSizeInitialized();
1584 int64_t offset = 0;
1585 nsresult res = mSeekable->Tell(&offset);
1586 NS_ENSURE_SUCCESS(res,res);
1587 res = mSeekable->Seek(nsISeekableStream::NS_SEEK_SET, aOffset);
1588 NS_ENSURE_SUCCESS(res,res);
1589 uint32_t bytesRead = 0;
1590 do {
1591 uint32_t x = 0;
1592 uint32_t bytesToRead = aCount - bytesRead;
1593 res = mInput->Read(aBuffer, bytesToRead, &x);
1594 bytesRead += x;
1595 } while (bytesRead != aCount && res == NS_OK);
1597 // Reset read head to original position so we don't disturb any other
1598 // reading thread.
1599 nsresult seekres = mSeekable->Seek(nsISeekableStream::NS_SEEK_SET, offset);
1601 // If a read failed in the loop above, we want to return its failure code.
1602 NS_ENSURE_SUCCESS(res,res);
1604 // Else we succeed if the reset-seek succeeds.
1605 return seekres;
1608 nsresult FileMediaResource::Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes)
1610 nsresult rv;
1611 int64_t offset = 0;
1613 MutexAutoLock lock(mLock);
1614 mSeekable->Tell(&offset);
1615 rv = UnsafeRead(aBuffer, aCount, aBytes);
1617 if (NS_SUCCEEDED(rv)) {
1618 DispatchBytesConsumed(*aBytes, offset);
1620 return rv;
1623 nsresult FileMediaResource::UnsafeRead(char* aBuffer, uint32_t aCount, uint32_t* aBytes)
1625 EnsureSizeInitialized();
1626 return mInput->Read(aBuffer, aCount, aBytes);
1629 nsresult FileMediaResource::ReadAt(int64_t aOffset, char* aBuffer,
1630 uint32_t aCount, uint32_t* aBytes)
1632 NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
1634 nsresult rv;
1636 MutexAutoLock lock(mLock);
1637 rv = UnsafeSeek(nsISeekableStream::NS_SEEK_SET, aOffset);
1638 if (NS_FAILED(rv)) return rv;
1639 rv = UnsafeRead(aBuffer, aCount, aBytes);
1641 if (NS_SUCCEEDED(rv)) {
1642 DispatchBytesConsumed(*aBytes, aOffset);
1644 return rv;
1647 nsresult FileMediaResource::Seek(int32_t aWhence, int64_t aOffset)
1649 NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
1651 MutexAutoLock lock(mLock);
1652 return UnsafeSeek(aWhence, aOffset);
1655 nsresult FileMediaResource::UnsafeSeek(int32_t aWhence, int64_t aOffset)
1657 NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
1659 if (!mSeekable)
1660 return NS_ERROR_FAILURE;
1661 EnsureSizeInitialized();
1662 return mSeekable->Seek(aWhence, aOffset);
1665 int64_t FileMediaResource::Tell()
1667 NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
1669 MutexAutoLock lock(mLock);
1670 if (!mSeekable)
1671 return 0;
1672 EnsureSizeInitialized();
1674 int64_t offset = 0;
1675 mSeekable->Tell(&offset);
1676 return offset;
1679 already_AddRefed<MediaResource>
1680 MediaResource::Create(MediaDecoder* aDecoder, nsIChannel* aChannel)
1682 NS_ASSERTION(NS_IsMainThread(),
1683 "MediaResource::Open called on non-main thread");
1685 // If the channel was redirected, we want the post-redirect URI;
1686 // but if the URI scheme was expanded, say from chrome: to jar:file:,
1687 // we want the original URI.
1688 nsCOMPtr<nsIURI> uri;
1689 nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
1690 NS_ENSURE_SUCCESS(rv, nullptr);
1692 nsAutoCString contentType;
1693 aChannel->GetContentType(contentType);
1695 nsCOMPtr<nsIFileChannel> fc = do_QueryInterface(aChannel);
1696 nsRefPtr<MediaResource> resource;
1697 if (fc || IsBlobURI(uri)) {
1698 resource = new FileMediaResource(aDecoder, aChannel, uri, contentType);
1699 } else if (IsRtspURI(uri)) {
1700 resource = new RtspMediaResource(aDecoder, aChannel, uri, contentType);
1701 } else {
1702 resource = new ChannelMediaResource(aDecoder, aChannel, uri, contentType);
1704 return resource.forget();
1707 void BaseMediaResource::MoveLoadsToBackground() {
1708 NS_ASSERTION(!mLoadInBackground, "Why are you calling this more than once?");
1709 mLoadInBackground = true;
1710 if (!mChannel) {
1711 // No channel, resource is probably already loaded.
1712 return;
1715 MediaDecoderOwner* owner = mDecoder->GetMediaOwner();
1716 if (!owner) {
1717 NS_WARNING("Null owner in MediaResource::MoveLoadsToBackground()");
1718 return;
1720 HTMLMediaElement* element = owner->GetMediaElement();
1721 if (!element) {
1722 NS_WARNING("Null element in MediaResource::MoveLoadsToBackground()");
1723 return;
1726 bool isPending = false;
1727 if (NS_SUCCEEDED(mChannel->IsPending(&isPending)) &&
1728 isPending) {
1729 nsLoadFlags loadFlags;
1730 DebugOnly<nsresult> rv = mChannel->GetLoadFlags(&loadFlags);
1731 NS_ASSERTION(NS_SUCCEEDED(rv), "GetLoadFlags() failed!");
1733 loadFlags |= nsIRequest::LOAD_BACKGROUND;
1734 ModifyLoadFlags(loadFlags);
1738 void BaseMediaResource::ModifyLoadFlags(nsLoadFlags aFlags)
1740 nsCOMPtr<nsILoadGroup> loadGroup;
1741 DebugOnly<nsresult> rv = mChannel->GetLoadGroup(getter_AddRefs(loadGroup));
1742 NS_ASSERTION(NS_SUCCEEDED(rv), "GetLoadGroup() failed!");
1744 nsresult status;
1745 mChannel->GetStatus(&status);
1747 // Note: if (NS_FAILED(status)), the channel won't be in the load group.
1748 if (loadGroup &&
1749 NS_SUCCEEDED(status)) {
1750 rv = loadGroup->RemoveRequest(mChannel, nullptr, status);
1751 NS_ASSERTION(NS_SUCCEEDED(rv), "RemoveRequest() failed!");
1754 rv = mChannel->SetLoadFlags(aFlags);
1755 NS_ASSERTION(NS_SUCCEEDED(rv), "SetLoadFlags() failed!");
1757 if (loadGroup &&
1758 NS_SUCCEEDED(status)) {
1759 rv = loadGroup->AddRequest(mChannel, nullptr);
1760 NS_ASSERTION(NS_SUCCEEDED(rv), "AddRequest() failed!");
1764 class DispatchBytesConsumedEvent : public nsRunnable {
1765 public:
1766 DispatchBytesConsumedEvent(MediaDecoder* aDecoder,
1767 int64_t aNumBytes,
1768 int64_t aOffset)
1769 : mDecoder(aDecoder),
1770 mNumBytes(aNumBytes),
1771 mOffset(aOffset)
1773 MOZ_COUNT_CTOR(DispatchBytesConsumedEvent);
1776 ~DispatchBytesConsumedEvent()
1778 MOZ_COUNT_DTOR(DispatchBytesConsumedEvent);
1781 NS_IMETHOD Run() {
1782 mDecoder->NotifyBytesConsumed(mNumBytes, mOffset);
1783 // Drop ref to decoder on main thread, just in case this reference
1784 // ends up being the last owning reference somehow.
1785 mDecoder = nullptr;
1786 return NS_OK;
1789 RefPtr<MediaDecoder> mDecoder;
1790 int64_t mNumBytes;
1791 int64_t mOffset;
1794 void BaseMediaResource::DispatchBytesConsumed(int64_t aNumBytes, int64_t aOffset)
1796 RefPtr<nsIRunnable> event(new DispatchBytesConsumedEvent(mDecoder, aNumBytes, aOffset));
1797 NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
1800 } // namespace mozilla