Line data Source code
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/. */
6 :
7 : #include "mozilla/DebugOnly.h"
8 :
9 : #include "DecoderTraits.h"
10 : #include "MediaResource.h"
11 : #include "MediaResourceCallback.h"
12 :
13 : #include "mozilla/Mutex.h"
14 : #include "nsDebug.h"
15 : #include "nsNetUtil.h"
16 : #include "nsThreadUtils.h"
17 : #include "nsIFile.h"
18 : #include "nsIFileChannel.h"
19 : #include "nsIFileStreams.h"
20 : #include "nsIHttpChannel.h"
21 : #include "nsISeekableStream.h"
22 : #include "nsIInputStream.h"
23 : #include "nsIRequestObserver.h"
24 : #include "nsIStreamListener.h"
25 : #include "nsIScriptSecurityManager.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"
34 : #include "nsIContentPolicy.h"
35 :
36 : using mozilla::media::TimeUnit;
37 :
38 : #undef LOG
39 : #undef ILOG
40 :
41 : mozilla::LazyLogModule gMediaResourceLog("MediaResource");
42 : // Debug logging macro with object pointer and class name.
43 : #define LOG(msg, ...) MOZ_LOG(gMediaResourceLog, mozilla::LogLevel::Debug, \
44 : ("%p " msg, this, ##__VA_ARGS__))
45 :
46 : mozilla::LazyLogModule gMediaResourceIndexLog("MediaResourceIndex");
47 : // Debug logging macro with object pointer and class name.
48 : #define ILOG(msg, ...) \
49 : MOZ_LOG(gMediaResourceIndexLog, \
50 : mozilla::LogLevel::Debug, \
51 : ("%p " msg, this, ##__VA_ARGS__))
52 :
53 : static const uint32_t HTTP_OK_CODE = 200;
54 : static const uint32_t HTTP_PARTIAL_RESPONSE_CODE = 206;
55 :
56 : namespace mozilla {
57 :
58 : void
59 0 : MediaResource::Destroy()
60 : {
61 : // Ensures we only delete the MediaResource on the main thread.
62 0 : if (NS_IsMainThread()) {
63 0 : delete this;
64 0 : return;
65 : }
66 0 : nsresult rv = SystemGroup::Dispatch(
67 : "MediaResource::Destroy",
68 : TaskCategory::Other,
69 0 : NewNonOwningRunnableMethod(
70 0 : "MediaResource::Destroy", this, &MediaResource::Destroy));
71 0 : MOZ_ALWAYS_SUCCEEDS(rv);
72 : }
73 :
74 0 : NS_IMPL_ADDREF(MediaResource)
75 0 : NS_IMPL_RELEASE_WITH_DESTROY(MediaResource, Destroy())
76 0 : NS_IMPL_QUERY_INTERFACE0(MediaResource)
77 :
78 0 : ChannelMediaResource::ChannelMediaResource(MediaResourceCallback* aCallback,
79 : nsIChannel* aChannel,
80 : nsIURI* aURI,
81 0 : bool aIsPrivateBrowsing)
82 : : BaseMediaResource(aCallback, aChannel, aURI)
83 : , mOffset(0)
84 : , mReopenOnError(false)
85 : , mIgnoreClose(false)
86 : , mCacheStream(this, aIsPrivateBrowsing)
87 : , mLock("ChannelMediaResource.mLock")
88 : , mIgnoreResume(false)
89 0 : , mSuspendAgent(mChannel)
90 : {
91 0 : }
92 :
93 0 : ChannelMediaResource::ChannelMediaResource(
94 : MediaResourceCallback* aCallback,
95 : nsIChannel* aChannel,
96 : nsIURI* aURI,
97 0 : const MediaChannelStatistics& aStatistics)
98 : : BaseMediaResource(aCallback, aChannel, aURI)
99 : , mOffset(0)
100 : , mReopenOnError(false)
101 : , mIgnoreClose(false)
102 : , mCacheStream(this, /* aIsPrivateBrowsing = */ false)
103 : , mLock("ChannelMediaResource.mLock")
104 : , mChannelStatistics(aStatistics)
105 : , mIgnoreResume(false)
106 0 : , mSuspendAgent(mChannel)
107 : {
108 0 : }
109 :
110 0 : ChannelMediaResource::~ChannelMediaResource()
111 : {
112 0 : if (mListener) {
113 : // Kill its reference to us since we're going away
114 0 : mListener->Revoke();
115 : }
116 0 : }
117 :
118 : // ChannelMediaResource::Listener just observes the channel and
119 : // forwards notifications to the ChannelMediaResource. We use multiple
120 : // listener objects so that when we open a new stream for a seek we can
121 : // disconnect the old listener from the ChannelMediaResource and hook up
122 : // a new listener, so notifications from the old channel are discarded
123 : // and don't confuse us.
124 0 : NS_IMPL_ISUPPORTS(ChannelMediaResource::Listener,
125 : nsIRequestObserver, nsIStreamListener, nsIChannelEventSink,
126 : nsIInterfaceRequestor)
127 :
128 : nsresult
129 0 : ChannelMediaResource::Listener::OnStartRequest(nsIRequest* aRequest,
130 : nsISupports* aContext)
131 : {
132 0 : if (!mResource)
133 0 : return NS_OK;
134 0 : return mResource->OnStartRequest(aRequest);
135 : }
136 :
137 : nsresult
138 0 : ChannelMediaResource::Listener::OnStopRequest(nsIRequest* aRequest,
139 : nsISupports* aContext,
140 : nsresult aStatus)
141 : {
142 0 : if (!mResource)
143 0 : return NS_OK;
144 0 : return mResource->OnStopRequest(aRequest, aStatus);
145 : }
146 :
147 : nsresult
148 0 : ChannelMediaResource::Listener::OnDataAvailable(nsIRequest* aRequest,
149 : nsISupports* aContext,
150 : nsIInputStream* aStream,
151 : uint64_t aOffset,
152 : uint32_t aCount)
153 : {
154 0 : if (!mResource)
155 0 : return NS_OK;
156 0 : return mResource->OnDataAvailable(aRequest, aStream, aCount);
157 : }
158 :
159 : nsresult
160 0 : ChannelMediaResource::Listener::AsyncOnChannelRedirect(nsIChannel* aOldChannel,
161 : nsIChannel* aNewChannel,
162 : uint32_t aFlags,
163 : nsIAsyncVerifyRedirectCallback* cb)
164 : {
165 0 : nsresult rv = NS_OK;
166 0 : if (mResource)
167 0 : rv = mResource->OnChannelRedirect(aOldChannel, aNewChannel, aFlags);
168 :
169 0 : if (NS_FAILED(rv))
170 0 : return rv;
171 :
172 0 : cb->OnRedirectVerifyCallback(NS_OK);
173 0 : return NS_OK;
174 : }
175 :
176 : nsresult
177 0 : ChannelMediaResource::Listener::GetInterface(const nsIID & aIID, void **aResult)
178 : {
179 0 : return QueryInterface(aIID, aResult);
180 : }
181 :
182 : static bool
183 0 : IsPayloadCompressed(nsIHttpChannel* aChannel)
184 : {
185 0 : nsAutoCString encoding;
186 0 : Unused << aChannel->GetResponseHeader(NS_LITERAL_CSTRING("Content-Encoding"), encoding);
187 0 : return encoding.Length() > 0;
188 : }
189 :
190 : nsresult
191 0 : ChannelMediaResource::OnStartRequest(nsIRequest* aRequest)
192 : {
193 0 : NS_ASSERTION(mChannel.get() == aRequest, "Wrong channel!");
194 :
195 0 : MediaDecoderOwner* owner = mCallback->GetMediaOwner();
196 0 : NS_ENSURE_TRUE(owner, NS_ERROR_FAILURE);
197 0 : dom::HTMLMediaElement* element = owner->GetMediaElement();
198 0 : NS_ENSURE_TRUE(element, NS_ERROR_FAILURE);
199 : nsresult status;
200 0 : nsresult rv = aRequest->GetStatus(&status);
201 0 : NS_ENSURE_SUCCESS(rv, rv);
202 :
203 0 : if (status == NS_BINDING_ABORTED) {
204 : // Request was aborted before we had a chance to receive any data, or
205 : // even an OnStartRequest(). Close the channel. This is important, as
206 : // we don't want to mess up our state, as if we're cloned that would
207 : // cause the clone to copy incorrect metadata (like whether we're
208 : // infinite for example).
209 0 : CloseChannel();
210 0 : return status;
211 : }
212 :
213 0 : if (element->ShouldCheckAllowOrigin()) {
214 : // If the request was cancelled by nsCORSListenerProxy due to failing
215 : // the CORS security check, send an error through to the media element.
216 0 : if (status == NS_ERROR_DOM_BAD_URI) {
217 0 : mCallback->NotifyNetworkError();
218 0 : return NS_ERROR_DOM_BAD_URI;
219 : }
220 : }
221 :
222 0 : nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(aRequest);
223 0 : bool seekable = false;
224 0 : if (hc) {
225 0 : uint32_t responseStatus = 0;
226 0 : Unused << hc->GetResponseStatus(&responseStatus);
227 0 : bool succeeded = false;
228 0 : Unused << hc->GetRequestSucceeded(&succeeded);
229 :
230 0 : if (!succeeded && NS_SUCCEEDED(status)) {
231 : // HTTP-level error (e.g. 4xx); treat this as a fatal network-level error.
232 : // We might get this on a seek.
233 : // (Note that lower-level errors indicated by NS_FAILED(status) are
234 : // handled in OnStopRequest.)
235 : // A 416 error should treated as EOF here... it's possible
236 : // that we don't get Content-Length, we read N bytes, then we
237 : // suspend and resume, the resume reopens the channel and we seek to
238 : // offset N, but there are no more bytes, so we get a 416
239 : // "Requested Range Not Satisfiable".
240 0 : if (responseStatus == HTTP_REQUESTED_RANGE_NOT_SATISFIABLE_CODE) {
241 : // OnStopRequest will not be fired, so we need to do some of its
242 : // work here.
243 0 : mCacheStream.NotifyDataEnded(status);
244 : } else {
245 0 : mCallback->NotifyNetworkError();
246 : }
247 :
248 : // This disconnects our listener so we don't get any more data. We
249 : // certainly don't want an error page to end up in our cache!
250 0 : CloseChannel();
251 0 : return NS_OK;
252 : }
253 :
254 0 : nsAutoCString ranges;
255 0 : Unused << hc->GetResponseHeader(NS_LITERAL_CSTRING("Accept-Ranges"),
256 0 : ranges);
257 0 : bool acceptsRanges = ranges.EqualsLiteral("bytes");
258 : // True if this channel will not return an unbounded amount of data
259 0 : bool dataIsBounded = false;
260 :
261 0 : int64_t contentLength = -1;
262 0 : const bool isCompressed = IsPayloadCompressed(hc);
263 0 : if (!isCompressed) {
264 0 : hc->GetContentLength(&contentLength);
265 : }
266 0 : if (contentLength >= 0 &&
267 0 : (responseStatus == HTTP_OK_CODE ||
268 0 : responseStatus == HTTP_PARTIAL_RESPONSE_CODE)) {
269 : // "OK" status means Content-Length is for the whole resource.
270 : // Since that's bounded, we know we have a finite-length resource.
271 0 : dataIsBounded = true;
272 : }
273 :
274 : // Assume Range requests have a bounded upper limit unless the
275 : // Content-Range header tells us otherwise.
276 0 : bool boundedSeekLimit = true;
277 : // Check response code for byte-range requests (seeking, chunk requests).
278 : // We don't expect to get a 206 response for a compressed stream, but
279 : // double check just to be sure.
280 0 : if (!isCompressed && responseStatus == HTTP_PARTIAL_RESPONSE_CODE) {
281 : // Parse Content-Range header.
282 0 : int64_t rangeStart = 0;
283 0 : int64_t rangeEnd = 0;
284 0 : int64_t rangeTotal = 0;
285 0 : rv = ParseContentRangeHeader(hc, rangeStart, rangeEnd, rangeTotal);
286 :
287 : // We received 'Content-Range', so the server accepts range requests.
288 0 : bool gotRangeHeader = NS_SUCCEEDED(rv);
289 :
290 0 : if (gotRangeHeader) {
291 : // We received 'Content-Range', so the server accepts range requests.
292 : // Notify media cache about the length and start offset of data received.
293 : // Note: If aRangeTotal == -1, then the total bytes is unknown at this stage.
294 : // For now, tell the decoder that the stream is infinite.
295 0 : if (rangeTotal == -1) {
296 0 : boundedSeekLimit = false;
297 : } else {
298 0 : contentLength = std::max(contentLength, rangeTotal);
299 : }
300 : // Give some warnings if the ranges are unexpected.
301 : // XXX These could be error conditions.
302 0 : NS_WARNING_ASSERTION(
303 : mOffset == rangeStart,
304 : "response range start does not match current offset");
305 0 : mOffset = rangeStart;
306 0 : mCacheStream.NotifyDataStarted(rangeStart);
307 : }
308 0 : acceptsRanges = gotRangeHeader;
309 0 : } else if (mOffset > 0 && responseStatus == HTTP_OK_CODE) {
310 : // If we get an OK response but we were seeking, or requesting a byte
311 : // range, then we have to assume that seeking doesn't work. We also need
312 : // to tell the cache that it's getting data for the start of the stream.
313 0 : mCacheStream.NotifyDataStarted(0);
314 0 : mOffset = 0;
315 :
316 : // The server claimed it supported range requests. It lied.
317 0 : acceptsRanges = false;
318 : }
319 0 : if (mOffset == 0 && contentLength >= 0 &&
320 0 : (responseStatus == HTTP_OK_CODE ||
321 0 : responseStatus == HTTP_PARTIAL_RESPONSE_CODE)) {
322 0 : mCacheStream.NotifyDataLength(contentLength);
323 : }
324 : // XXX we probably should examine the Content-Range header in case
325 : // the server gave us a range which is not quite what we asked for
326 :
327 : // If we get an HTTP_OK_CODE response to our byte range request,
328 : // and the server isn't sending Accept-Ranges:bytes then we don't
329 : // support seeking. We also can't seek in compressed streams.
330 0 : seekable = !isCompressed && acceptsRanges;
331 0 : if (seekable && boundedSeekLimit) {
332 : // If range requests are supported, and we did not see an unbounded
333 : // upper range limit, we assume the resource is bounded.
334 0 : dataIsBounded = true;
335 : }
336 :
337 0 : mCallback->SetInfinite(!dataIsBounded);
338 : }
339 0 : mCacheStream.SetTransportSeekable(seekable);
340 :
341 : {
342 0 : MutexAutoLock lock(mLock);
343 0 : mChannelStatistics.Start();
344 : }
345 :
346 0 : mReopenOnError = false;
347 0 : mIgnoreClose = false;
348 :
349 0 : mSuspendAgent.UpdateSuspendedStatusIfNeeded();
350 :
351 : // Fires an initial progress event.
352 0 : owner->DownloadProgressed();
353 :
354 0 : return NS_OK;
355 : }
356 :
357 : bool
358 0 : ChannelMediaResource::IsTransportSeekable()
359 : {
360 0 : return mCacheStream.IsTransportSeekable();
361 : }
362 :
363 : nsresult
364 0 : ChannelMediaResource::ParseContentRangeHeader(nsIHttpChannel * aHttpChan,
365 : int64_t& aRangeStart,
366 : int64_t& aRangeEnd,
367 : int64_t& aRangeTotal)
368 : {
369 0 : NS_ENSURE_ARG(aHttpChan);
370 :
371 0 : nsAutoCString rangeStr;
372 0 : nsresult rv = aHttpChan->GetResponseHeader(NS_LITERAL_CSTRING("Content-Range"),
373 0 : rangeStr);
374 0 : NS_ENSURE_SUCCESS(rv, rv);
375 0 : NS_ENSURE_FALSE(rangeStr.IsEmpty(), NS_ERROR_ILLEGAL_VALUE);
376 :
377 : // Parse the range header: e.g. Content-Range: bytes 7000-7999/8000.
378 0 : int32_t spacePos = rangeStr.Find(NS_LITERAL_CSTRING(" "));
379 0 : int32_t dashPos = rangeStr.Find(NS_LITERAL_CSTRING("-"), true, spacePos);
380 0 : int32_t slashPos = rangeStr.Find(NS_LITERAL_CSTRING("/"), true, dashPos);
381 :
382 0 : nsAutoCString aRangeStartText;
383 0 : rangeStr.Mid(aRangeStartText, spacePos+1, dashPos-(spacePos+1));
384 0 : aRangeStart = aRangeStartText.ToInteger64(&rv);
385 0 : NS_ENSURE_SUCCESS(rv, rv);
386 0 : NS_ENSURE_TRUE(0 <= aRangeStart, NS_ERROR_ILLEGAL_VALUE);
387 :
388 0 : nsAutoCString aRangeEndText;
389 0 : rangeStr.Mid(aRangeEndText, dashPos+1, slashPos-(dashPos+1));
390 0 : aRangeEnd = aRangeEndText.ToInteger64(&rv);
391 0 : NS_ENSURE_SUCCESS(rv, rv);
392 0 : NS_ENSURE_TRUE(aRangeStart < aRangeEnd, NS_ERROR_ILLEGAL_VALUE);
393 :
394 0 : nsAutoCString aRangeTotalText;
395 0 : rangeStr.Right(aRangeTotalText, rangeStr.Length()-(slashPos+1));
396 0 : if (aRangeTotalText[0] == '*') {
397 0 : aRangeTotal = -1;
398 : } else {
399 0 : aRangeTotal = aRangeTotalText.ToInteger64(&rv);
400 0 : NS_ENSURE_TRUE(aRangeEnd < aRangeTotal, NS_ERROR_ILLEGAL_VALUE);
401 0 : NS_ENSURE_SUCCESS(rv, rv);
402 : }
403 :
404 0 : LOG("Received bytes [%" PRId64 "] to [%" PRId64 "] of [%" PRId64 "] for decoder[%p]",
405 : aRangeStart, aRangeEnd, aRangeTotal, mCallback.get());
406 :
407 0 : return NS_OK;
408 : }
409 :
410 : nsresult
411 0 : ChannelMediaResource::OnStopRequest(nsIRequest* aRequest, nsresult aStatus)
412 : {
413 0 : NS_ASSERTION(mChannel.get() == aRequest, "Wrong channel!");
414 0 : NS_ASSERTION(!mSuspendAgent.IsSuspended(),
415 : "How can OnStopRequest fire while we're suspended?");
416 :
417 : {
418 0 : MutexAutoLock lock(mLock);
419 0 : mChannelStatistics.Stop();
420 : }
421 :
422 : // Note that aStatus might have succeeded --- this might be a normal close
423 : // --- even in situations where the server cut us off because we were
424 : // suspended. So we need to "reopen on error" in that case too. The only
425 : // cases where we don't need to reopen are when *we* closed the stream.
426 : // But don't reopen if we need to seek and we don't think we can... that would
427 : // cause us to just re-read the stream, which would be really bad.
428 0 : if (mReopenOnError && aStatus != NS_ERROR_PARSED_DATA_CACHED &&
429 0 : aStatus != NS_BINDING_ABORTED &&
430 0 : (mOffset == 0 || (GetLength() > 0 && mOffset != GetLength() &&
431 0 : mCacheStream.IsTransportSeekable()))) {
432 : // If the stream did close normally, restart the channel if we're either
433 : // at the start of the resource, or if the server is seekable and we're
434 : // not at the end of stream. We don't restart the stream if we're at the
435 : // end because not all web servers handle this case consistently; see:
436 : // https://bugzilla.mozilla.org/show_bug.cgi?id=1373618#c36
437 0 : nsresult rv = CacheClientSeek(mOffset, false);
438 0 : if (NS_SUCCEEDED(rv)) {
439 0 : return rv;
440 : }
441 : // If the reopen/reseek fails, just fall through and treat this
442 : // error as fatal.
443 : }
444 :
445 0 : if (!mIgnoreClose) {
446 0 : mCacheStream.NotifyDataEnded(aStatus);
447 :
448 : // Move this request back into the foreground. This is necessary for
449 : // requests owned by video documents to ensure the load group fires
450 : // OnStopRequest when restoring from session history.
451 : nsLoadFlags loadFlags;
452 0 : DebugOnly<nsresult> rv = mChannel->GetLoadFlags(&loadFlags);
453 0 : NS_ASSERTION(NS_SUCCEEDED(rv), "GetLoadFlags() failed!");
454 :
455 0 : if (loadFlags & nsIRequest::LOAD_BACKGROUND) {
456 0 : ModifyLoadFlags(loadFlags & ~nsIRequest::LOAD_BACKGROUND);
457 : }
458 : }
459 :
460 0 : return NS_OK;
461 : }
462 :
463 : nsresult
464 0 : ChannelMediaResource::OnChannelRedirect(nsIChannel* aOld, nsIChannel* aNew,
465 : uint32_t aFlags)
466 : {
467 0 : mChannel = aNew;
468 0 : mSuspendAgent.NotifyChannelOpened(mChannel);
469 0 : return SetupChannelHeaders();
470 : }
471 :
472 : nsresult
473 0 : ChannelMediaResource::CopySegmentToCache(nsIPrincipal* aPrincipal,
474 : const char* aFromSegment,
475 : uint32_t aCount,
476 : uint32_t* aWriteCount)
477 : {
478 : // Keep track of where we're up to.
479 0 : LOG("CopySegmentToCache at mOffset [%" PRId64 "] add "
480 : "[%d] bytes for decoder[%p]",
481 : mOffset, aCount, mCallback.get());
482 0 : mOffset += aCount;
483 0 : mCacheStream.NotifyDataReceived(aCount, aFromSegment, aPrincipal);
484 0 : *aWriteCount = aCount;
485 0 : return NS_OK;
486 : }
487 :
488 :
489 0 : struct CopySegmentClosure {
490 : nsCOMPtr<nsIPrincipal> mPrincipal;
491 : ChannelMediaResource* mResource;
492 : };
493 :
494 : nsresult
495 0 : ChannelMediaResource::CopySegmentToCache(nsIInputStream* aInStream,
496 : void* aClosure,
497 : const char* aFromSegment,
498 : uint32_t aToOffset,
499 : uint32_t aCount,
500 : uint32_t* aWriteCount)
501 : {
502 0 : CopySegmentClosure* closure = static_cast<CopySegmentClosure*>(aClosure);
503 0 : return closure->mResource->CopySegmentToCache(
504 0 : closure->mPrincipal, aFromSegment, aCount, aWriteCount);
505 : }
506 :
507 : nsresult
508 0 : ChannelMediaResource::OnDataAvailable(nsIRequest* aRequest,
509 : nsIInputStream* aStream,
510 : uint32_t aCount)
511 : {
512 0 : NS_ASSERTION(mChannel.get() == aRequest, "Wrong channel!");
513 :
514 : {
515 0 : MutexAutoLock lock(mLock);
516 0 : mChannelStatistics.AddBytes(aCount);
517 : }
518 :
519 0 : CopySegmentClosure closure;
520 0 : nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
521 0 : if (secMan && mChannel) {
522 0 : secMan->GetChannelResultPrincipal(mChannel, getter_AddRefs(closure.mPrincipal));
523 : }
524 0 : closure.mResource = this;
525 :
526 0 : uint32_t count = aCount;
527 0 : while (count > 0) {
528 : uint32_t read;
529 : nsresult rv = aStream->ReadSegments(CopySegmentToCache, &closure, count,
530 0 : &read);
531 0 : if (NS_FAILED(rv))
532 0 : return rv;
533 0 : NS_ASSERTION(read > 0, "Read 0 bytes while data was available?");
534 0 : count -= read;
535 : }
536 :
537 0 : return NS_OK;
538 : }
539 :
540 0 : nsresult ChannelMediaResource::Open(nsIStreamListener **aStreamListener)
541 : {
542 0 : NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
543 :
544 0 : int64_t cl = -1;
545 0 : if (mChannel) {
546 0 : nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(mChannel);
547 0 : if (hc && !IsPayloadCompressed(hc)) {
548 0 : if (NS_FAILED(hc->GetContentLength(&cl))) {
549 0 : cl = -1;
550 : }
551 : }
552 : }
553 :
554 0 : nsresult rv = mCacheStream.Init(cl);
555 0 : if (NS_FAILED(rv))
556 0 : return rv;
557 0 : NS_ASSERTION(mOffset == 0, "Who set mOffset already?");
558 :
559 0 : if (!mChannel) {
560 : // When we're a clone, the decoder might ask us to Open even though
561 : // we haven't established an mChannel (because we might not need one)
562 0 : NS_ASSERTION(!aStreamListener,
563 : "Should have already been given a channel if we're to return a stream listener");
564 0 : return NS_OK;
565 : }
566 :
567 0 : return OpenChannel(aStreamListener);
568 : }
569 :
570 0 : nsresult ChannelMediaResource::OpenChannel(nsIStreamListener** aStreamListener)
571 : {
572 0 : NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
573 0 : NS_ENSURE_TRUE(mChannel, NS_ERROR_NULL_POINTER);
574 0 : NS_ASSERTION(!mListener, "Listener should have been removed by now");
575 :
576 0 : if (aStreamListener) {
577 0 : *aStreamListener = nullptr;
578 : }
579 :
580 : // Set the content length, if it's available as an HTTP header.
581 : // This ensures that MediaResource wrapping objects for platform libraries
582 : // that expect to know the length of a resource can get it before
583 : // OnStartRequest() fires.
584 0 : nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(mChannel);
585 0 : if (hc && !IsPayloadCompressed(hc)) {
586 0 : int64_t cl = -1;
587 0 : if (NS_SUCCEEDED(hc->GetContentLength(&cl)) && cl != -1) {
588 0 : mCacheStream.NotifyDataLength(cl);
589 : }
590 : }
591 :
592 0 : mListener = new Listener(this);
593 0 : if (aStreamListener) {
594 0 : *aStreamListener = mListener;
595 0 : NS_ADDREF(*aStreamListener);
596 : } else {
597 0 : nsresult rv = mChannel->SetNotificationCallbacks(mListener.get());
598 0 : NS_ENSURE_SUCCESS(rv, rv);
599 :
600 0 : rv = SetupChannelHeaders();
601 0 : NS_ENSURE_SUCCESS(rv, rv);
602 :
603 0 : rv = mChannel->AsyncOpen2(mListener);
604 0 : NS_ENSURE_SUCCESS(rv, rv);
605 :
606 : // Tell the media element that we are fetching data from a channel.
607 0 : MediaDecoderOwner* owner = mCallback->GetMediaOwner();
608 0 : NS_ENSURE_TRUE(owner, NS_ERROR_FAILURE);
609 0 : dom::HTMLMediaElement* element = owner->GetMediaElement();
610 0 : element->DownloadResumed(true);
611 : }
612 :
613 0 : return NS_OK;
614 : }
615 :
616 0 : nsresult ChannelMediaResource::SetupChannelHeaders()
617 : {
618 : // Always use a byte range request even if we're reading from the start
619 : // of the resource.
620 : // This enables us to detect if the stream supports byte range
621 : // requests, and therefore seeking, early.
622 0 : nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(mChannel);
623 0 : if (hc) {
624 : // Use |mOffset| if seeking in a complete file download.
625 0 : nsAutoCString rangeString("bytes=");
626 0 : rangeString.AppendInt(mOffset);
627 0 : rangeString.Append('-');
628 0 : nsresult rv = hc->SetRequestHeader(NS_LITERAL_CSTRING("Range"), rangeString, false);
629 0 : NS_ENSURE_SUCCESS(rv, rv);
630 :
631 : // Send Accept header for video and audio types only (Bug 489071)
632 0 : NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread");
633 0 : MediaDecoderOwner* owner = mCallback->GetMediaOwner();
634 0 : NS_ENSURE_TRUE(owner, NS_ERROR_FAILURE);
635 0 : dom::HTMLMediaElement* element = owner->GetMediaElement();
636 0 : NS_ENSURE_TRUE(element, NS_ERROR_FAILURE);
637 0 : element->SetRequestHeaders(hc);
638 : } else {
639 0 : NS_ASSERTION(mOffset == 0, "Don't know how to seek on this channel type");
640 0 : return NS_ERROR_FAILURE;
641 : }
642 0 : return NS_OK;
643 : }
644 :
645 0 : nsresult ChannelMediaResource::Close()
646 : {
647 0 : NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
648 :
649 0 : mCacheStream.Close();
650 0 : CloseChannel();
651 0 : return NS_OK;
652 : }
653 :
654 0 : already_AddRefed<nsIPrincipal> ChannelMediaResource::GetCurrentPrincipal()
655 : {
656 0 : NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
657 :
658 0 : nsCOMPtr<nsIPrincipal> principal = mCacheStream.GetCurrentPrincipal();
659 0 : return principal.forget();
660 : }
661 :
662 0 : bool ChannelMediaResource::CanClone()
663 : {
664 0 : return mCacheStream.IsAvailableForSharing();
665 : }
666 :
667 0 : already_AddRefed<MediaResource> ChannelMediaResource::CloneData(MediaResourceCallback* aCallback)
668 : {
669 0 : NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
670 0 : NS_ASSERTION(mCacheStream.IsAvailableForSharing(), "Stream can't be cloned");
671 :
672 : RefPtr<ChannelMediaResource> resource =
673 0 : new ChannelMediaResource(aCallback, nullptr, mURI, mChannelStatistics);
674 0 : if (resource) {
675 : // Initially the clone is treated as suspended by the cache, because
676 : // we don't have a channel. If the cache needs to read data from the clone
677 : // it will call CacheClientResume (or CacheClientSeek with aResume true)
678 : // which will recreate the channel. This way, if all of the media data
679 : // is already in the cache we don't create an unnecessary HTTP channel
680 : // and perform a useless HTTP transaction.
681 0 : resource->mSuspendAgent.Suspend();
682 0 : resource->mCacheStream.InitAsClone(&mCacheStream);
683 0 : resource->mChannelStatistics.Stop();
684 : }
685 0 : return resource.forget();
686 : }
687 :
688 0 : void ChannelMediaResource::CloseChannel()
689 : {
690 0 : NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
691 :
692 : {
693 0 : MutexAutoLock lock(mLock);
694 0 : mChannelStatistics.Stop();
695 : }
696 :
697 0 : if (mListener) {
698 0 : mListener->Revoke();
699 0 : mListener = nullptr;
700 : }
701 :
702 0 : if (mChannel) {
703 0 : mSuspendAgent.NotifyChannelClosing();
704 : // The status we use here won't be passed to the decoder, since
705 : // we've already revoked the listener. It can however be passed
706 : // to nsDocumentViewer::LoadComplete if our channel is the one
707 : // that kicked off creation of a video document. We don't want that
708 : // document load to think there was an error.
709 : // NS_ERROR_PARSED_DATA_CACHED is the best thing we have for that
710 : // at the moment.
711 0 : mChannel->Cancel(NS_ERROR_PARSED_DATA_CACHED);
712 0 : mChannel = nullptr;
713 : }
714 0 : }
715 :
716 0 : nsresult ChannelMediaResource::ReadFromCache(char* aBuffer,
717 : int64_t aOffset,
718 : uint32_t aCount)
719 : {
720 0 : return mCacheStream.ReadFromCache(aBuffer, aOffset, aCount);
721 : }
722 :
723 0 : nsresult ChannelMediaResource::ReadAt(int64_t aOffset,
724 : char* aBuffer,
725 : uint32_t aCount,
726 : uint32_t* aBytes)
727 : {
728 0 : NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
729 :
730 0 : nsresult rv = mCacheStream.ReadAt(aOffset, aBuffer, aCount, aBytes);
731 0 : if (NS_SUCCEEDED(rv)) {
732 0 : DispatchBytesConsumed(*aBytes, aOffset);
733 : }
734 0 : return rv;
735 : }
736 :
737 : void
738 0 : ChannelMediaResource::ThrottleReadahead(bool bThrottle)
739 : {
740 0 : mCacheStream.ThrottleReadahead(bThrottle);
741 0 : }
742 :
743 0 : int64_t ChannelMediaResource::Tell()
744 : {
745 0 : return mCacheStream.Tell();
746 : }
747 :
748 0 : nsresult ChannelMediaResource::GetCachedRanges(MediaByteRangeSet& aRanges)
749 : {
750 0 : return mCacheStream.GetCachedRanges(aRanges);
751 : }
752 :
753 0 : void ChannelMediaResource::Suspend(bool aCloseImmediately)
754 : {
755 0 : NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread");
756 :
757 0 : MediaDecoderOwner* owner = mCallback->GetMediaOwner();
758 0 : if (!owner) {
759 : // Shutting down; do nothing.
760 0 : return;
761 : }
762 0 : dom::HTMLMediaElement* element = owner->GetMediaElement();
763 0 : if (!element) {
764 : // Shutting down; do nothing.
765 0 : return;
766 : }
767 :
768 0 : if (mChannel && aCloseImmediately && mCacheStream.IsTransportSeekable()) {
769 : // Kill off our channel right now, but don't tell anyone about it.
770 0 : mIgnoreClose = true;
771 0 : CloseChannel();
772 0 : element->DownloadSuspended();
773 : }
774 :
775 0 : if (mSuspendAgent.Suspend()) {
776 0 : if (mChannel) {
777 : {
778 0 : MutexAutoLock lock(mLock);
779 0 : mChannelStatistics.Stop();
780 : }
781 0 : element->DownloadSuspended();
782 : }
783 : }
784 : }
785 :
786 0 : void ChannelMediaResource::Resume()
787 : {
788 0 : NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread");
789 :
790 0 : MediaDecoderOwner* owner = mCallback->GetMediaOwner();
791 0 : if (!owner) {
792 : // Shutting down; do nothing.
793 0 : return;
794 : }
795 0 : dom::HTMLMediaElement* element = owner->GetMediaElement();
796 0 : if (!element) {
797 : // Shutting down; do nothing.
798 0 : return;
799 : }
800 :
801 0 : if (mSuspendAgent.Resume()) {
802 0 : if (mChannel) {
803 : // Just wake up our existing channel
804 : {
805 0 : MutexAutoLock lock(mLock);
806 0 : mChannelStatistics.Start();
807 : }
808 : // if an error occurs after Resume, assume it's because the server
809 : // timed out the connection and we should reopen it.
810 0 : mReopenOnError = true;
811 0 : element->DownloadResumed();
812 : } else {
813 0 : int64_t totalLength = mCacheStream.GetLength();
814 : // If mOffset is at the end of the stream, then we shouldn't try to
815 : // seek to it. The seek will fail and be wasted anyway. We can leave
816 : // the channel dead; if the media cache wants to read some other data
817 : // in the future, it will call CacheClientSeek itself which will reopen the
818 : // channel.
819 0 : if (totalLength < 0 || mOffset < totalLength) {
820 : // There is (or may be) data to read at mOffset, so start reading it.
821 : // Need to recreate the channel.
822 0 : CacheClientSeek(mOffset, false);
823 0 : element->DownloadResumed();
824 : } else {
825 : // The channel remains dead. Do not notify DownloadResumed() which
826 : // will leave the media element in NETWORK_LOADING state.
827 : }
828 : }
829 : }
830 : }
831 :
832 : nsresult
833 0 : ChannelMediaResource::RecreateChannel()
834 : {
835 : nsLoadFlags loadFlags =
836 : nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE_IF_BUSY |
837 0 : nsIChannel::LOAD_CLASSIFY_URI |
838 0 : (mLoadInBackground ? nsIRequest::LOAD_BACKGROUND : 0);
839 :
840 0 : MediaDecoderOwner* owner = mCallback->GetMediaOwner();
841 0 : if (!owner) {
842 : // The decoder is being shut down, so don't bother opening a new channel
843 0 : return NS_OK;
844 : }
845 0 : dom::HTMLMediaElement* element = owner->GetMediaElement();
846 0 : if (!element) {
847 : // The decoder is being shut down, so don't bother opening a new channel
848 0 : return NS_OK;
849 : }
850 0 : nsCOMPtr<nsILoadGroup> loadGroup = element->GetDocumentLoadGroup();
851 0 : NS_ENSURE_TRUE(loadGroup, NS_ERROR_NULL_POINTER);
852 :
853 0 : nsSecurityFlags securityFlags = element->ShouldCheckAllowOrigin()
854 0 : ? nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS
855 0 : : nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS;
856 :
857 0 : MOZ_ASSERT(element->IsAnyOfHTMLElements(nsGkAtoms::audio, nsGkAtoms::video));
858 0 : nsContentPolicyType contentPolicyType = element->IsHTMLElement(nsGkAtoms::audio) ?
859 0 : nsIContentPolicy::TYPE_INTERNAL_AUDIO : nsIContentPolicy::TYPE_INTERNAL_VIDEO;
860 :
861 0 : nsresult rv = NS_NewChannel(getter_AddRefs(mChannel),
862 : mURI,
863 : element,
864 : securityFlags,
865 : contentPolicyType,
866 : loadGroup,
867 : nullptr, // aCallbacks
868 0 : loadFlags);
869 0 : NS_ENSURE_SUCCESS(rv, rv);
870 :
871 0 : mSuspendAgent.NotifyChannelOpened(mChannel);
872 :
873 : // Tell the cache to reset the download status when the channel is reopened.
874 0 : mCacheStream.NotifyChannelRecreated();
875 :
876 0 : return rv;
877 : }
878 :
879 : void
880 0 : ChannelMediaResource::DoNotifyDataReceived()
881 : {
882 0 : mDataReceivedEvent.Revoke();
883 0 : mCallback->NotifyDataArrived();
884 0 : }
885 :
886 : void
887 0 : ChannelMediaResource::CacheClientNotifyDataReceived()
888 : {
889 0 : NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread");
890 : // NOTE: this can be called with the media cache lock held, so don't
891 : // block or do anything which might try to acquire a lock!
892 :
893 0 : if (mDataReceivedEvent.IsPending())
894 0 : return;
895 :
896 : mDataReceivedEvent =
897 0 : NewNonOwningRunnableMethod("ChannelMediaResource::DoNotifyDataReceived",
898 0 : this, &ChannelMediaResource::DoNotifyDataReceived);
899 :
900 0 : nsCOMPtr<nsIRunnable> event = mDataReceivedEvent.get();
901 :
902 0 : SystemGroup::AbstractMainThreadFor(TaskCategory::Other)->Dispatch(event.forget());
903 : }
904 :
905 : void
906 0 : ChannelMediaResource::CacheClientNotifyDataEnded(nsresult aStatus)
907 : {
908 0 : MOZ_ASSERT(NS_IsMainThread());
909 0 : mCallback->NotifyDataEnded(aStatus);
910 0 : }
911 :
912 : void
913 0 : ChannelMediaResource::CacheClientNotifyPrincipalChanged()
914 : {
915 0 : NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread");
916 :
917 0 : mCallback->NotifyPrincipalChanged();
918 0 : }
919 :
920 : void
921 0 : ChannelMediaResource::CacheClientNotifySuspendedStatusChanged()
922 : {
923 0 : NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread");
924 0 : mCallback->NotifySuspendedStatusChanged();
925 0 : }
926 :
927 : nsresult
928 0 : ChannelMediaResource::CacheClientSeek(int64_t aOffset, bool aResume)
929 : {
930 0 : NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread");
931 :
932 0 : LOG("CacheClientSeek requested for aOffset [%" PRId64 "] for decoder [%p]",
933 : aOffset, mCallback.get());
934 :
935 0 : CloseChannel();
936 :
937 0 : mOffset = aOffset;
938 :
939 : // Don't report close of the channel because the channel is not closed for
940 : // download ended, but for internal changes in the read position.
941 0 : mIgnoreClose = true;
942 :
943 0 : if (aResume) {
944 0 : mSuspendAgent.Resume();
945 : }
946 :
947 : // Don't create a new channel if we are still suspended. The channel will
948 : // be recreated when we are resumed.
949 0 : if (mSuspendAgent.IsSuspended()) {
950 0 : return NS_OK;
951 : }
952 :
953 0 : nsresult rv = RecreateChannel();
954 0 : NS_ENSURE_SUCCESS(rv, rv);
955 :
956 0 : return OpenChannel(nullptr);
957 : }
958 :
959 : nsresult
960 0 : ChannelMediaResource::CacheClientSuspend()
961 : {
962 0 : Suspend(false);
963 0 : return NS_OK;
964 : }
965 :
966 : nsresult
967 0 : ChannelMediaResource::CacheClientResume()
968 : {
969 0 : Resume();
970 0 : return NS_OK;
971 : }
972 :
973 : int64_t
974 0 : ChannelMediaResource::GetNextCachedData(int64_t aOffset)
975 : {
976 0 : return mCacheStream.GetNextCachedData(aOffset);
977 : }
978 :
979 : int64_t
980 0 : ChannelMediaResource::GetCachedDataEnd(int64_t aOffset)
981 : {
982 0 : return mCacheStream.GetCachedDataEnd(aOffset);
983 : }
984 :
985 : bool
986 0 : ChannelMediaResource::IsDataCachedToEndOfResource(int64_t aOffset)
987 : {
988 0 : return mCacheStream.IsDataCachedToEndOfStream(aOffset);
989 : }
990 :
991 : void
992 0 : ChannelMediaResource::EnsureCacheUpToDate()
993 : {
994 0 : mCacheStream.EnsureCacheUpdate();
995 0 : }
996 :
997 : bool
998 0 : ChannelMediaResource::IsSuspendedByCache()
999 : {
1000 0 : return mCacheStream.AreAllStreamsForResourceSuspended();
1001 : }
1002 :
1003 : bool
1004 0 : ChannelMediaResource::IsSuspended()
1005 : {
1006 0 : return mSuspendAgent.IsSuspended();
1007 : }
1008 :
1009 : void
1010 0 : ChannelMediaResource::SetReadMode(MediaCacheStream::ReadMode aMode)
1011 : {
1012 0 : mCacheStream.SetReadMode(aMode);
1013 0 : }
1014 :
1015 : void
1016 0 : ChannelMediaResource::SetPlaybackRate(uint32_t aBytesPerSecond)
1017 : {
1018 0 : mCacheStream.SetPlaybackRate(aBytesPerSecond);
1019 0 : }
1020 :
1021 : void
1022 0 : ChannelMediaResource::Pin()
1023 : {
1024 0 : mCacheStream.Pin();
1025 0 : }
1026 :
1027 : void
1028 0 : ChannelMediaResource::Unpin()
1029 : {
1030 0 : mCacheStream.Unpin();
1031 0 : }
1032 :
1033 : double
1034 0 : ChannelMediaResource::GetDownloadRate(bool* aIsReliable)
1035 : {
1036 0 : MutexAutoLock lock(mLock);
1037 0 : return mChannelStatistics.GetRate(aIsReliable);
1038 : }
1039 :
1040 : int64_t
1041 0 : ChannelMediaResource::GetLength()
1042 : {
1043 0 : return mCacheStream.GetLength();
1044 : }
1045 :
1046 : // ChannelSuspendAgent
1047 :
1048 : bool
1049 0 : ChannelSuspendAgent::Suspend()
1050 : {
1051 0 : SuspendInternal();
1052 0 : return (++mSuspendCount == 1);
1053 : }
1054 :
1055 : void
1056 0 : ChannelSuspendAgent::SuspendInternal()
1057 : {
1058 0 : if (mChannel) {
1059 0 : bool isPending = false;
1060 0 : nsresult rv = mChannel->IsPending(&isPending);
1061 0 : if (NS_SUCCEEDED(rv) && isPending && !mIsChannelSuspended) {
1062 0 : mChannel->Suspend();
1063 0 : mIsChannelSuspended = true;
1064 : }
1065 : }
1066 0 : }
1067 :
1068 : bool
1069 0 : ChannelSuspendAgent::Resume()
1070 : {
1071 0 : MOZ_ASSERT(IsSuspended(), "Resume without suspend!");
1072 0 : --mSuspendCount;
1073 :
1074 0 : if (mSuspendCount == 0) {
1075 0 : if (mChannel && mIsChannelSuspended) {
1076 0 : mChannel->Resume();
1077 0 : mIsChannelSuspended = false;
1078 : }
1079 0 : return true;
1080 : }
1081 0 : return false;
1082 : }
1083 :
1084 : void
1085 0 : ChannelSuspendAgent::UpdateSuspendedStatusIfNeeded()
1086 : {
1087 0 : if (!mIsChannelSuspended && IsSuspended()) {
1088 0 : SuspendInternal();
1089 : }
1090 0 : }
1091 :
1092 : void
1093 0 : ChannelSuspendAgent::NotifyChannelOpened(nsIChannel* aChannel)
1094 : {
1095 0 : MOZ_ASSERT(aChannel);
1096 0 : mChannel = aChannel;
1097 0 : }
1098 :
1099 : void
1100 0 : ChannelSuspendAgent::NotifyChannelClosing()
1101 : {
1102 0 : MOZ_ASSERT(mChannel);
1103 : // Before close the channel, it need to be resumed to make sure its internal
1104 : // state is correct. Besides, We need to suspend the channel after recreating.
1105 0 : if (mIsChannelSuspended) {
1106 0 : mChannel->Resume();
1107 0 : mIsChannelSuspended = false;
1108 : }
1109 0 : mChannel = nullptr;
1110 0 : }
1111 :
1112 : bool
1113 0 : ChannelSuspendAgent::IsSuspended()
1114 : {
1115 0 : return (mSuspendCount > 0);
1116 : }
1117 :
1118 : // FileMediaResource
1119 :
1120 : class FileMediaResource : public BaseMediaResource
1121 : {
1122 : public:
1123 0 : FileMediaResource(MediaResourceCallback* aCallback,
1124 : nsIChannel* aChannel,
1125 : nsIURI* aURI)
1126 0 : : BaseMediaResource(aCallback, aChannel, aURI)
1127 : , mSize(-1)
1128 : , mLock("FileMediaResource.mLock")
1129 0 : , mSizeInitialized(false)
1130 : {
1131 0 : }
1132 0 : ~FileMediaResource()
1133 0 : {
1134 0 : }
1135 :
1136 : // Main thread
1137 : nsresult Open(nsIStreamListener** aStreamListener) override;
1138 : nsresult Close() override;
1139 0 : void Suspend(bool aCloseImmediately) override {}
1140 0 : void Resume() override {}
1141 : already_AddRefed<nsIPrincipal> GetCurrentPrincipal() override;
1142 : nsresult ReadFromCache(char* aBuffer, int64_t aOffset, uint32_t aCount) override;
1143 :
1144 : // These methods are called off the main thread.
1145 :
1146 : // Other thread
1147 0 : void SetReadMode(MediaCacheStream::ReadMode aMode) override {}
1148 0 : void SetPlaybackRate(uint32_t aBytesPerSecond) override {}
1149 : nsresult ReadAt(int64_t aOffset, char* aBuffer,
1150 : uint32_t aCount, uint32_t* aBytes) override;
1151 : // (Probably) file-based, caching recommended.
1152 0 : bool ShouldCacheReads() override { return true; }
1153 : int64_t Tell() override;
1154 :
1155 : // Any thread
1156 0 : void Pin() override {}
1157 0 : void Unpin() override {}
1158 0 : double GetDownloadRate(bool* aIsReliable) override
1159 : {
1160 : // The data's all already here
1161 0 : *aIsReliable = true;
1162 0 : return 100*1024*1024; // arbitray, use 100MB/s
1163 : }
1164 0 : int64_t GetLength() override {
1165 0 : MutexAutoLock lock(mLock);
1166 :
1167 0 : EnsureSizeInitialized();
1168 0 : return mSizeInitialized ? mSize : 0;
1169 : }
1170 0 : int64_t GetNextCachedData(int64_t aOffset) override
1171 : {
1172 0 : MutexAutoLock lock(mLock);
1173 :
1174 0 : EnsureSizeInitialized();
1175 0 : return (aOffset < mSize) ? aOffset : -1;
1176 : }
1177 0 : int64_t GetCachedDataEnd(int64_t aOffset) override {
1178 0 : MutexAutoLock lock(mLock);
1179 :
1180 0 : EnsureSizeInitialized();
1181 0 : return std::max(aOffset, mSize);
1182 : }
1183 0 : bool IsDataCachedToEndOfResource(int64_t aOffset) override { return true; }
1184 0 : bool IsSuspendedByCache() override { return true; }
1185 0 : bool IsSuspended() override { return true; }
1186 0 : bool IsTransportSeekable() override { return true; }
1187 :
1188 : nsresult GetCachedRanges(MediaByteRangeSet& aRanges) override;
1189 :
1190 0 : size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override
1191 : {
1192 : // Might be useful to track in the future:
1193 : // - mInput
1194 0 : return BaseMediaResource::SizeOfExcludingThis(aMallocSizeOf);
1195 : }
1196 :
1197 0 : size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override
1198 : {
1199 0 : return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
1200 : }
1201 :
1202 : protected:
1203 : // These Unsafe variants of Read and Seek perform their operations
1204 : // without acquiring mLock. The caller must obtain the lock before
1205 : // calling. The implmentation of Read, Seek and ReadAt obtains the
1206 : // lock before calling these Unsafe variants to read or seek.
1207 : nsresult UnsafeRead(char* aBuffer, uint32_t aCount, uint32_t* aBytes);
1208 : nsresult UnsafeSeek(int32_t aWhence, int64_t aOffset);
1209 : private:
1210 : // Ensures mSize is initialized, if it can be.
1211 : // mLock must be held when this is called, and mInput must be non-null.
1212 : void EnsureSizeInitialized();
1213 : already_AddRefed<MediaByteBuffer> UnsafeMediaReadAt(
1214 : int64_t aOffset, uint32_t aCount);
1215 :
1216 : // The file size, or -1 if not known. Immutable after Open().
1217 : // Can be used from any thread.
1218 : int64_t mSize;
1219 :
1220 : // This lock handles synchronisation between calls to Close() and
1221 : // the Read, Seek, etc calls. Close must not be called while a
1222 : // Read or Seek is in progress since it resets various internal
1223 : // values to null.
1224 : // This lock protects mSeekable, mInput, mSize, and mSizeInitialized.
1225 : Mutex mLock;
1226 :
1227 : // Seekable stream interface to file. This can be used from any
1228 : // thread.
1229 : nsCOMPtr<nsISeekableStream> mSeekable;
1230 :
1231 : // Input stream for the media data. This can be used from any
1232 : // thread.
1233 : nsCOMPtr<nsIInputStream> mInput;
1234 :
1235 : // Whether we've attempted to initialize mSize. Note that mSize can be -1
1236 : // when mSizeInitialized is true if we tried and failed to get the size
1237 : // of the file.
1238 : bool mSizeInitialized;
1239 : };
1240 :
1241 0 : void FileMediaResource::EnsureSizeInitialized()
1242 : {
1243 0 : mLock.AssertCurrentThreadOwns();
1244 0 : NS_ASSERTION(mInput, "Must have file input stream");
1245 0 : if (mSizeInitialized) {
1246 0 : return;
1247 : }
1248 0 : mSizeInitialized = true;
1249 : // Get the file size and inform the decoder.
1250 : uint64_t size;
1251 0 : nsresult res = mInput->Available(&size);
1252 0 : if (NS_SUCCEEDED(res) && size <= INT64_MAX) {
1253 0 : mSize = (int64_t)size;
1254 0 : mCallback->NotifyDataEnded(NS_OK);
1255 : }
1256 : }
1257 :
1258 0 : nsresult FileMediaResource::GetCachedRanges(MediaByteRangeSet& aRanges)
1259 : {
1260 0 : MutexAutoLock lock(mLock);
1261 :
1262 0 : EnsureSizeInitialized();
1263 0 : if (mSize == -1) {
1264 0 : return NS_ERROR_FAILURE;
1265 : }
1266 0 : aRanges += MediaByteRange(0, mSize);
1267 0 : return NS_OK;
1268 : }
1269 :
1270 0 : nsresult FileMediaResource::Open(nsIStreamListener** aStreamListener)
1271 : {
1272 0 : NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
1273 0 : MOZ_ASSERT(aStreamListener);
1274 :
1275 0 : *aStreamListener = nullptr;
1276 0 : nsresult rv = NS_OK;
1277 :
1278 : // The channel is already open. We need a synchronous stream that
1279 : // implements nsISeekableStream, so we have to find the underlying
1280 : // file and reopen it
1281 0 : nsCOMPtr<nsIFileChannel> fc(do_QueryInterface(mChannel));
1282 0 : if (fc) {
1283 0 : nsCOMPtr<nsIFile> file;
1284 0 : rv = fc->GetFile(getter_AddRefs(file));
1285 0 : NS_ENSURE_SUCCESS(rv, rv);
1286 :
1287 0 : rv = NS_NewLocalFileInputStream(
1288 0 : getter_AddRefs(mInput), file, -1, -1, nsIFileInputStream::SHARE_DELETE);
1289 0 : } else if (IsBlobURI(mURI)) {
1290 0 : rv = NS_GetStreamForBlobURI(mURI, getter_AddRefs(mInput));
1291 : }
1292 :
1293 0 : NS_ENSURE_SUCCESS(rv, rv);
1294 :
1295 0 : mSeekable = do_QueryInterface(mInput);
1296 0 : if (!mSeekable) {
1297 : // XXX The file may just be a .url or similar
1298 : // shortcut that points to a Web site. We need to fix this by
1299 : // doing an async open and waiting until we locate the real resource,
1300 : // then using that (if it's still a file!).
1301 0 : return NS_ERROR_FAILURE;
1302 : }
1303 :
1304 0 : return NS_OK;
1305 : }
1306 :
1307 0 : nsresult FileMediaResource::Close()
1308 : {
1309 0 : NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
1310 :
1311 : // Since mChennel is only accessed by main thread, there is no necessary to
1312 : // take the lock.
1313 0 : if (mChannel) {
1314 0 : mChannel->Cancel(NS_ERROR_PARSED_DATA_CACHED);
1315 0 : mChannel = nullptr;
1316 : }
1317 :
1318 0 : return NS_OK;
1319 : }
1320 :
1321 0 : already_AddRefed<nsIPrincipal> FileMediaResource::GetCurrentPrincipal()
1322 : {
1323 0 : NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
1324 :
1325 0 : nsCOMPtr<nsIPrincipal> principal;
1326 0 : nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
1327 0 : if (!secMan || !mChannel)
1328 0 : return nullptr;
1329 0 : secMan->GetChannelResultPrincipal(mChannel, getter_AddRefs(principal));
1330 0 : return principal.forget();
1331 : }
1332 :
1333 0 : nsresult FileMediaResource::ReadFromCache(char* aBuffer, int64_t aOffset, uint32_t aCount)
1334 : {
1335 0 : MutexAutoLock lock(mLock);
1336 :
1337 0 : EnsureSizeInitialized();
1338 0 : if (!aCount) {
1339 0 : return NS_OK;
1340 : }
1341 0 : int64_t offset = 0;
1342 0 : nsresult res = mSeekable->Tell(&offset);
1343 0 : NS_ENSURE_SUCCESS(res,res);
1344 0 : res = mSeekable->Seek(nsISeekableStream::NS_SEEK_SET, aOffset);
1345 0 : NS_ENSURE_SUCCESS(res,res);
1346 0 : uint32_t bytesRead = 0;
1347 0 : do {
1348 0 : uint32_t x = 0;
1349 0 : uint32_t bytesToRead = aCount - bytesRead;
1350 0 : res = mInput->Read(aBuffer, bytesToRead, &x);
1351 0 : bytesRead += x;
1352 0 : if (!x) {
1353 0 : res = NS_ERROR_FAILURE;
1354 : }
1355 0 : } while (bytesRead != aCount && res == NS_OK);
1356 :
1357 : // Reset read head to original position so we don't disturb any other
1358 : // reading thread.
1359 0 : nsresult seekres = mSeekable->Seek(nsISeekableStream::NS_SEEK_SET, offset);
1360 :
1361 : // If a read failed in the loop above, we want to return its failure code.
1362 0 : NS_ENSURE_SUCCESS(res,res);
1363 :
1364 : // Else we succeed if the reset-seek succeeds.
1365 0 : return seekres;
1366 : }
1367 :
1368 0 : nsresult FileMediaResource::UnsafeRead(char* aBuffer, uint32_t aCount, uint32_t* aBytes)
1369 : {
1370 0 : EnsureSizeInitialized();
1371 0 : return mInput->Read(aBuffer, aCount, aBytes);
1372 : }
1373 :
1374 0 : nsresult FileMediaResource::ReadAt(int64_t aOffset, char* aBuffer,
1375 : uint32_t aCount, uint32_t* aBytes)
1376 : {
1377 0 : NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
1378 :
1379 : nsresult rv;
1380 : {
1381 0 : MutexAutoLock lock(mLock);
1382 0 : rv = UnsafeSeek(nsISeekableStream::NS_SEEK_SET, aOffset);
1383 0 : if (NS_FAILED(rv)) return rv;
1384 0 : rv = UnsafeRead(aBuffer, aCount, aBytes);
1385 : }
1386 0 : if (NS_SUCCEEDED(rv)) {
1387 0 : DispatchBytesConsumed(*aBytes, aOffset);
1388 : }
1389 0 : return rv;
1390 : }
1391 :
1392 : already_AddRefed<MediaByteBuffer>
1393 0 : FileMediaResource::UnsafeMediaReadAt(int64_t aOffset, uint32_t aCount)
1394 : {
1395 0 : RefPtr<MediaByteBuffer> bytes = new MediaByteBuffer();
1396 0 : bool ok = bytes->SetLength(aCount, fallible);
1397 0 : NS_ENSURE_TRUE(ok, nullptr);
1398 0 : nsresult rv = UnsafeSeek(nsISeekableStream::NS_SEEK_SET, aOffset);
1399 0 : NS_ENSURE_SUCCESS(rv, nullptr);
1400 0 : char* curr = reinterpret_cast<char*>(bytes->Elements());
1401 0 : const char* start = curr;
1402 0 : while (aCount > 0) {
1403 : uint32_t bytesRead;
1404 0 : rv = UnsafeRead(curr, aCount, &bytesRead);
1405 0 : NS_ENSURE_SUCCESS(rv, nullptr);
1406 0 : if (!bytesRead) {
1407 0 : break;
1408 : }
1409 0 : aCount -= bytesRead;
1410 0 : curr += bytesRead;
1411 : }
1412 0 : bytes->SetLength(curr - start);
1413 0 : return bytes.forget();
1414 : }
1415 :
1416 0 : nsresult FileMediaResource::UnsafeSeek(int32_t aWhence, int64_t aOffset)
1417 : {
1418 0 : NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
1419 :
1420 0 : if (!mSeekable)
1421 0 : return NS_ERROR_FAILURE;
1422 0 : EnsureSizeInitialized();
1423 0 : return mSeekable->Seek(aWhence, aOffset);
1424 : }
1425 :
1426 0 : int64_t FileMediaResource::Tell()
1427 : {
1428 0 : MutexAutoLock lock(mLock);
1429 0 : EnsureSizeInitialized();
1430 :
1431 0 : int64_t offset = 0;
1432 : // Return mSize as offset (end of stream) in case of error
1433 0 : if (!mSeekable || NS_FAILED(mSeekable->Tell(&offset)))
1434 0 : return mSize;
1435 0 : return offset;
1436 : }
1437 :
1438 : already_AddRefed<MediaResource>
1439 0 : MediaResource::Create(MediaResourceCallback* aCallback,
1440 : nsIChannel* aChannel, bool aIsPrivateBrowsing)
1441 : {
1442 0 : NS_ASSERTION(NS_IsMainThread(),
1443 : "MediaResource::Open called on non-main thread");
1444 :
1445 : // If the channel was redirected, we want the post-redirect URI;
1446 : // but if the URI scheme was expanded, say from chrome: to jar:file:,
1447 : // we want the original URI.
1448 0 : nsCOMPtr<nsIURI> uri;
1449 0 : nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
1450 0 : NS_ENSURE_SUCCESS(rv, nullptr);
1451 :
1452 0 : nsAutoCString contentTypeString;
1453 0 : aChannel->GetContentType(contentTypeString);
1454 0 : Maybe<MediaContainerType> containerType = MakeMediaContainerType(contentTypeString);
1455 0 : if (!containerType) {
1456 0 : return nullptr;
1457 : }
1458 :
1459 0 : RefPtr<MediaResource> resource;
1460 :
1461 : // Let's try to create a FileMediaResource in case the channel is a nsIFile
1462 0 : nsCOMPtr<nsIFileChannel> fc = do_QueryInterface(aChannel);
1463 0 : if (fc) {
1464 0 : resource = new FileMediaResource(aCallback, aChannel, uri);
1465 : }
1466 :
1467 : // If the URL is blobURL with a seekable inputStream, we can still use a
1468 : // FileMediaResource. This basically means that the blobURL and its Blob have
1469 : // been created in the current process.
1470 :
1471 0 : if (!resource) {
1472 0 : nsCOMPtr<nsIInputStream> stream;
1473 0 : nsCOMPtr<nsISeekableStream> seekableStream;
1474 0 : if (IsBlobURI(uri) &&
1475 0 : NS_SUCCEEDED(NS_GetStreamForBlobURI(uri, getter_AddRefs(stream))) &&
1476 0 : (seekableStream = do_QueryInterface(stream))) {
1477 0 : resource = new FileMediaResource(aCallback, aChannel, uri);
1478 : }
1479 : }
1480 :
1481 0 : if (!resource) {
1482 : resource =
1483 0 : new ChannelMediaResource(aCallback, aChannel, uri, aIsPrivateBrowsing);
1484 : }
1485 :
1486 0 : return resource.forget();
1487 : }
1488 :
1489 0 : void BaseMediaResource::SetLoadInBackground(bool aLoadInBackground) {
1490 0 : if (aLoadInBackground == mLoadInBackground) {
1491 0 : return;
1492 : }
1493 0 : mLoadInBackground = aLoadInBackground;
1494 0 : if (!mChannel) {
1495 : // No channel, resource is probably already loaded.
1496 0 : return;
1497 : }
1498 :
1499 0 : MediaDecoderOwner* owner = mCallback->GetMediaOwner();
1500 0 : if (!owner) {
1501 0 : NS_WARNING("Null owner in MediaResource::SetLoadInBackground()");
1502 0 : return;
1503 : }
1504 0 : dom::HTMLMediaElement* element = owner->GetMediaElement();
1505 0 : if (!element) {
1506 0 : NS_WARNING("Null element in MediaResource::SetLoadInBackground()");
1507 0 : return;
1508 : }
1509 :
1510 0 : bool isPending = false;
1511 0 : if (NS_SUCCEEDED(mChannel->IsPending(&isPending)) &&
1512 : isPending) {
1513 : nsLoadFlags loadFlags;
1514 0 : DebugOnly<nsresult> rv = mChannel->GetLoadFlags(&loadFlags);
1515 0 : NS_ASSERTION(NS_SUCCEEDED(rv), "GetLoadFlags() failed!");
1516 :
1517 0 : if (aLoadInBackground) {
1518 0 : loadFlags |= nsIRequest::LOAD_BACKGROUND;
1519 : } else {
1520 0 : loadFlags &= ~nsIRequest::LOAD_BACKGROUND;
1521 : }
1522 0 : ModifyLoadFlags(loadFlags);
1523 : }
1524 : }
1525 :
1526 0 : void BaseMediaResource::ModifyLoadFlags(nsLoadFlags aFlags)
1527 : {
1528 0 : nsCOMPtr<nsILoadGroup> loadGroup;
1529 0 : nsresult rv = mChannel->GetLoadGroup(getter_AddRefs(loadGroup));
1530 0 : MOZ_ASSERT(NS_SUCCEEDED(rv), "GetLoadGroup() failed!");
1531 :
1532 : nsresult status;
1533 0 : mChannel->GetStatus(&status);
1534 :
1535 0 : bool inLoadGroup = false;
1536 0 : if (loadGroup) {
1537 0 : rv = loadGroup->RemoveRequest(mChannel, nullptr, status);
1538 0 : if (NS_SUCCEEDED(rv)) {
1539 0 : inLoadGroup = true;
1540 : }
1541 : }
1542 :
1543 0 : rv = mChannel->SetLoadFlags(aFlags);
1544 0 : MOZ_ASSERT(NS_SUCCEEDED(rv), "SetLoadFlags() failed!");
1545 :
1546 0 : if (inLoadGroup) {
1547 0 : rv = loadGroup->AddRequest(mChannel, nullptr);
1548 0 : MOZ_ASSERT(NS_SUCCEEDED(rv), "AddRequest() failed!");
1549 : }
1550 0 : }
1551 :
1552 0 : void BaseMediaResource::DispatchBytesConsumed(int64_t aNumBytes, int64_t aOffset)
1553 : {
1554 0 : if (aNumBytes <= 0) {
1555 0 : return;
1556 : }
1557 0 : mCallback->NotifyBytesConsumed(aNumBytes, aOffset);
1558 : }
1559 :
1560 : nsresult
1561 0 : MediaResourceIndex::Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes)
1562 : {
1563 0 : NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
1564 :
1565 : // We purposefuly don't check that we may attempt to read past
1566 : // mResource->GetLength() as the resource's length may change over time.
1567 :
1568 0 : nsresult rv = ReadAt(mOffset, aBuffer, aCount, aBytes);
1569 0 : if (NS_FAILED(rv)) {
1570 0 : return rv;
1571 : }
1572 0 : mOffset += *aBytes;
1573 0 : return NS_OK;
1574 : }
1575 :
1576 : static nsCString
1577 0 : ResultName(nsresult aResult)
1578 : {
1579 0 : nsCString name;
1580 0 : GetErrorName(aResult, name);
1581 0 : return name;
1582 : }
1583 :
1584 : nsresult
1585 0 : MediaResourceIndex::ReadAt(int64_t aOffset,
1586 : char* aBuffer,
1587 : uint32_t aCount,
1588 : uint32_t* aBytes)
1589 : {
1590 0 : if (mCacheBlockSize == 0) {
1591 0 : return UncachedReadAt(aOffset, aBuffer, aCount, aBytes);
1592 : }
1593 :
1594 0 : *aBytes = 0;
1595 :
1596 0 : if (aCount == 0) {
1597 0 : return NS_OK;
1598 : }
1599 :
1600 0 : const int64_t endOffset = aOffset + aCount;
1601 0 : const int64_t lastBlockOffset = CacheOffsetContaining(endOffset - 1);
1602 :
1603 0 : if (mCachedBytes != 0 && mCachedOffset + mCachedBytes >= aOffset &&
1604 0 : mCachedOffset < endOffset) {
1605 : // There is data in the cache that is not completely before aOffset and not
1606 : // completely after endOffset, so it could be usable (with potential top-up).
1607 0 : if (aOffset < mCachedOffset) {
1608 : // We need to read before the cached data.
1609 0 : const uint32_t toRead = uint32_t(mCachedOffset - aOffset);
1610 0 : MOZ_ASSERT(toRead > 0);
1611 0 : MOZ_ASSERT(toRead < aCount);
1612 0 : uint32_t read = 0;
1613 0 : nsresult rv = UncachedReadAt(aOffset, aBuffer, toRead, &read);
1614 0 : if (NS_FAILED(rv)) {
1615 0 : ILOG("ReadAt(%" PRIu32 "@%" PRId64
1616 : ") uncached read before cache -> %s, %" PRIu32,
1617 : aCount,
1618 : aOffset,
1619 : ResultName(rv).get(),
1620 : *aBytes);
1621 0 : return rv;
1622 : }
1623 0 : *aBytes = read;
1624 0 : if (read < toRead) {
1625 : // Could not read everything we wanted, we're done.
1626 0 : ILOG("ReadAt(%" PRIu32 "@%" PRId64
1627 : ") uncached read before cache, incomplete -> OK, %" PRIu32,
1628 : aCount,
1629 : aOffset,
1630 : *aBytes);
1631 0 : return NS_OK;
1632 : }
1633 0 : ILOG("ReadAt(%" PRIu32 "@%" PRId64
1634 : ") uncached read before cache: %" PRIu32 ", remaining: %" PRIu32
1635 : "@%" PRId64 "...",
1636 : aCount,
1637 : aOffset,
1638 : read,
1639 : aCount - read,
1640 : aOffset + read);
1641 0 : aOffset += read;
1642 0 : aBuffer += read;
1643 0 : aCount -= read;
1644 : // We should have reached the cache.
1645 0 : MOZ_ASSERT(aOffset == mCachedOffset);
1646 : }
1647 0 : MOZ_ASSERT(aOffset >= mCachedOffset);
1648 :
1649 : // We've reached our cache.
1650 : const uint32_t toCopy =
1651 0 : std::min(aCount, uint32_t(mCachedOffset + mCachedBytes - aOffset));
1652 : // Note that we could in fact be just after the last byte of the cache, in
1653 : // which case we can't actually read from it! (But we will top-up next.)
1654 0 : if (toCopy != 0) {
1655 0 : memcpy(aBuffer, &mCachedBlock[IndexInCache(aOffset)], toCopy);
1656 0 : *aBytes += toCopy;
1657 0 : aCount -= toCopy;
1658 0 : if (aCount == 0) {
1659 : // All done!
1660 0 : ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") copied everything (%" PRIu32
1661 : ") from cache(%" PRIu32 "@%" PRId64 ") :-D -> OK, %" PRIu32,
1662 : aCount,
1663 : aOffset,
1664 : toCopy,
1665 : mCachedBytes,
1666 : mCachedOffset,
1667 : *aBytes);
1668 0 : return NS_OK;
1669 : }
1670 0 : aOffset += toCopy;
1671 0 : aBuffer += toCopy;
1672 0 : ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") copied %" PRIu32
1673 : " from cache(%" PRIu32 "@%" PRId64 ") :-), remaining: %" PRIu32
1674 : "@%" PRId64 "...",
1675 : aCount + toCopy,
1676 : aOffset - toCopy,
1677 : toCopy,
1678 : mCachedBytes,
1679 : mCachedOffset,
1680 : aCount,
1681 : aOffset);
1682 : }
1683 :
1684 0 : if (aOffset - 1 >= lastBlockOffset) {
1685 : // We were already reading cached data from the last block, we need more
1686 : // from it -> try to top-up, read what we can, and we'll be done.
1687 0 : MOZ_ASSERT(aOffset == mCachedOffset + mCachedBytes);
1688 0 : MOZ_ASSERT(endOffset <= lastBlockOffset + mCacheBlockSize);
1689 0 : return CacheOrReadAt(aOffset, aBuffer, aCount, aBytes);
1690 : }
1691 :
1692 : // We were not in the last block (but we may just have crossed the line now)
1693 0 : MOZ_ASSERT(aOffset <= lastBlockOffset);
1694 : // Continue below...
1695 0 : } else if (aOffset >= lastBlockOffset) {
1696 : // There was nothing we could get from the cache.
1697 : // But we're already in the last block -> Cache or read what we can.
1698 : // Make sure to invalidate the cache first.
1699 0 : mCachedBytes = 0;
1700 0 : return CacheOrReadAt(aOffset, aBuffer, aCount, aBytes);
1701 : }
1702 :
1703 : // If we're here, either there was nothing usable in the cache, or we've just
1704 : // read what was in the cache but there's still more to read.
1705 :
1706 0 : if (aOffset < lastBlockOffset) {
1707 : // We need to read before the last block.
1708 : // Start with an uncached read up to the last block.
1709 0 : const uint32_t toRead = uint32_t(lastBlockOffset - aOffset);
1710 0 : MOZ_ASSERT(toRead > 0);
1711 0 : MOZ_ASSERT(toRead < aCount);
1712 0 : uint32_t read = 0;
1713 0 : nsresult rv = UncachedReadAt(aOffset, aBuffer, toRead, &read);
1714 0 : if (NS_FAILED(rv)) {
1715 0 : ILOG("ReadAt(%" PRIu32 "@%" PRId64
1716 : ") uncached read before last block failed -> %s, %" PRIu32,
1717 : aCount,
1718 : aOffset,
1719 : ResultName(rv).get(),
1720 : *aBytes);
1721 0 : return rv;
1722 : }
1723 0 : if (read == 0) {
1724 0 : ILOG("ReadAt(%" PRIu32 "@%" PRId64
1725 : ") uncached read 0 before last block -> OK, %" PRIu32,
1726 : aCount,
1727 : aOffset,
1728 : *aBytes);
1729 0 : return NS_OK;
1730 : }
1731 0 : *aBytes += read;
1732 0 : if (read < toRead) {
1733 : // Could not read everything we wanted, we're done.
1734 0 : ILOG("ReadAt(%" PRIu32 "@%" PRId64
1735 : ") uncached read before last block, incomplete -> OK, %" PRIu32,
1736 : aCount,
1737 : aOffset,
1738 : *aBytes);
1739 0 : return NS_OK;
1740 : }
1741 0 : ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") read %" PRIu32
1742 : " before last block, remaining: %" PRIu32 "@%" PRId64 "...",
1743 : aCount,
1744 : aOffset,
1745 : read,
1746 : aCount - read,
1747 : aOffset + read);
1748 0 : aOffset += read;
1749 0 : aBuffer += read;
1750 0 : aCount -= read;
1751 : }
1752 :
1753 : // We should just have reached the start of the last block.
1754 0 : MOZ_ASSERT(aOffset == lastBlockOffset);
1755 0 : MOZ_ASSERT(aCount <= mCacheBlockSize);
1756 : // Make sure to invalidate the cache first.
1757 0 : mCachedBytes = 0;
1758 0 : return CacheOrReadAt(aOffset, aBuffer, aCount, aBytes);
1759 : }
1760 :
1761 : nsresult
1762 0 : MediaResourceIndex::CacheOrReadAt(int64_t aOffset,
1763 : char* aBuffer,
1764 : uint32_t aCount,
1765 : uint32_t* aBytes)
1766 : {
1767 : // We should be here because there is more data to read.
1768 0 : MOZ_ASSERT(aCount > 0);
1769 : // We should be in the last block, so we shouldn't try to read past it.
1770 0 : MOZ_ASSERT(IndexInCache(aOffset) + aCount <= mCacheBlockSize);
1771 :
1772 0 : const int64_t length = GetLength();
1773 : // If length is unknown (-1), look at resource-cached data.
1774 : // If length is known and equal or greater than requested, also look at
1775 : // resource-cached data.
1776 : // Otherwise, if length is known but same, or less than(!?), requested, don't
1777 : // attempt to access resource-cached data, as we're not expecting it to ever
1778 : // be greater than the length.
1779 0 : if (length < 0 || length >= aOffset + aCount) {
1780 : // Is there cached data covering at least the requested range?
1781 0 : const int64_t cachedDataEnd = mResource->GetCachedDataEnd(aOffset);
1782 0 : if (cachedDataEnd >= aOffset + aCount) {
1783 : // Try to read as much resource-cached data as can fill our local cache.
1784 : // Assume we can read as much as is cached without blocking.
1785 0 : const uint32_t cacheIndex = IndexInCache(aOffset);
1786 : const uint32_t toRead =
1787 0 : uint32_t(std::min(cachedDataEnd - aOffset,
1788 0 : int64_t(mCacheBlockSize - cacheIndex)));
1789 0 : MOZ_ASSERT(toRead >= aCount);
1790 0 : uint32_t read = 0;
1791 : // We would like `toRead` if possible, but ok with at least `aCount`.
1792 0 : nsresult rv = UncachedRangedReadAt(
1793 0 : aOffset, &mCachedBlock[cacheIndex], aCount, toRead - aCount, &read);
1794 0 : if (NS_SUCCEEDED(rv)) {
1795 0 : if (read == 0) {
1796 0 : ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") - UncachedRangedReadAt(%" PRIu32
1797 : "..%" PRIu32 "@%" PRId64
1798 : ") to top-up succeeded but read nothing -> OK anyway",
1799 : aCount,
1800 : aOffset,
1801 : aCount,
1802 : toRead,
1803 : aOffset);
1804 : // Couldn't actually read anything, but didn't error out, so count
1805 : // that as success.
1806 0 : return NS_OK;
1807 : }
1808 0 : if (mCachedOffset + mCachedBytes == aOffset) {
1809 : // We were topping-up the cache, just update its size.
1810 0 : ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") - UncachedRangedReadAt(%" PRIu32
1811 : "..%" PRIu32 "@%" PRId64 ") to top-up succeeded to read %" PRIu32
1812 : "...",
1813 : aCount,
1814 : aOffset,
1815 : aCount,
1816 : toRead,
1817 : aOffset,
1818 : read);
1819 0 : mCachedBytes += read;
1820 : } else {
1821 : // We were filling the cache from scratch, save new cache information.
1822 0 : ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") - UncachedRangedReadAt(%" PRIu32
1823 : "..%" PRIu32 "@%" PRId64
1824 : ") to fill cache succeeded to read %" PRIu32 "...",
1825 : aCount,
1826 : aOffset,
1827 : aCount,
1828 : toRead,
1829 : aOffset,
1830 : read);
1831 0 : mCachedOffset = aOffset;
1832 0 : mCachedBytes = read;
1833 : }
1834 : // Copy relevant part into output.
1835 0 : uint32_t toCopy = std::min(aCount, read);
1836 0 : memcpy(aBuffer, &mCachedBlock[cacheIndex], toCopy);
1837 0 : *aBytes += toCopy;
1838 0 : ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") - copied %" PRIu32 "@%" PRId64
1839 : " -> OK, %" PRIu32,
1840 : aCount,
1841 : aOffset,
1842 : toCopy,
1843 : aOffset,
1844 : *aBytes);
1845 : // We may not have read all that was requested, but we got everything
1846 : // we could get, so we're done.
1847 0 : return NS_OK;
1848 : }
1849 0 : ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") - UncachedRangedReadAt(%" PRIu32
1850 : "..%" PRIu32 "@%" PRId64
1851 : ") failed: %s, will fallback to blocking read...",
1852 : aCount,
1853 : aOffset,
1854 : aCount,
1855 : toRead,
1856 : aOffset,
1857 : ResultName(rv).get());
1858 : // Failure during reading. Note that this may be due to the cache
1859 : // changing between `GetCachedDataEnd` and `ReadAt`, so it's not
1860 : // totally unexpected, just hopefully rare; but we do need to handle it.
1861 :
1862 : // Invalidate part of cache that may have been partially overridden.
1863 0 : if (mCachedOffset + mCachedBytes == aOffset) {
1864 : // We were topping-up the cache, just keep the old untouched data.
1865 : // (i.e., nothing to do here.)
1866 : } else {
1867 : // We were filling the cache from scratch, invalidate cache.
1868 0 : mCachedBytes = 0;
1869 : }
1870 : } else {
1871 0 : ILOG("ReadAt(%" PRIu32 "@%" PRId64
1872 : ") - no cached data, will fallback to blocking read...",
1873 : aCount,
1874 : aOffset);
1875 0 : }
1876 : } else {
1877 0 : ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") - length is %" PRId64
1878 : " (%s), will fallback to blocking read as the caller requested...",
1879 : aCount,
1880 : aOffset,
1881 : length,
1882 : length < 0 ? "unknown" : "too short!");
1883 : }
1884 0 : uint32_t read = 0;
1885 0 : nsresult rv = UncachedReadAt(aOffset, aBuffer, aCount, &read);
1886 0 : if (NS_SUCCEEDED(rv)) {
1887 0 : *aBytes += read;
1888 0 : ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") - fallback uncached read got %" PRIu32
1889 : " bytes -> %s, %" PRIu32,
1890 : aCount,
1891 : aOffset,
1892 : read,
1893 : ResultName(rv).get(),
1894 : *aBytes);
1895 : } else {
1896 0 : ILOG("ReadAt(%" PRIu32 "@%" PRId64
1897 : ") - fallback uncached read failed -> %s, %" PRIu32,
1898 : aCount,
1899 : aOffset,
1900 : ResultName(rv).get(),
1901 : *aBytes);
1902 : }
1903 0 : return rv;
1904 : }
1905 :
1906 : nsresult
1907 0 : MediaResourceIndex::UncachedReadAt(int64_t aOffset,
1908 : char* aBuffer,
1909 : uint32_t aCount,
1910 : uint32_t* aBytes) const
1911 : {
1912 0 : *aBytes = 0;
1913 0 : if (aCount != 0) {
1914 : for (;;) {
1915 0 : uint32_t bytesRead = 0;
1916 0 : nsresult rv = mResource->ReadAt(aOffset, aBuffer, aCount, &bytesRead);
1917 0 : if (NS_FAILED(rv)) {
1918 0 : return rv;
1919 : }
1920 0 : if (bytesRead == 0) {
1921 0 : break;
1922 : }
1923 0 : *aBytes += bytesRead;
1924 0 : aCount -= bytesRead;
1925 0 : if (aCount == 0) {
1926 0 : break;
1927 : }
1928 0 : aOffset += bytesRead;
1929 0 : aBuffer += bytesRead;
1930 0 : }
1931 : }
1932 0 : return NS_OK;
1933 : }
1934 :
1935 : nsresult
1936 0 : MediaResourceIndex::UncachedRangedReadAt(int64_t aOffset,
1937 : char* aBuffer,
1938 : uint32_t aRequestedCount,
1939 : uint32_t aExtraCount,
1940 : uint32_t* aBytes) const
1941 : {
1942 0 : *aBytes = 0;
1943 0 : uint32_t count = aRequestedCount + aExtraCount;
1944 0 : if (count != 0) {
1945 : for (;;) {
1946 0 : uint32_t bytesRead = 0;
1947 0 : nsresult rv = mResource->ReadAt(aOffset, aBuffer, count, &bytesRead);
1948 0 : if (NS_FAILED(rv)) {
1949 0 : return rv;
1950 : }
1951 0 : if (bytesRead == 0) {
1952 0 : break;
1953 : }
1954 0 : *aBytes += bytesRead;
1955 0 : count -= bytesRead;
1956 0 : if (count <= aExtraCount) {
1957 : // We have read at least aRequestedCount, don't loop anymore.
1958 0 : break;
1959 : }
1960 0 : aOffset += bytesRead;
1961 0 : aBuffer += bytesRead;
1962 0 : }
1963 : }
1964 0 : return NS_OK;
1965 : }
1966 :
1967 : nsresult
1968 0 : MediaResourceIndex::Seek(int32_t aWhence, int64_t aOffset)
1969 : {
1970 0 : switch (aWhence) {
1971 : case SEEK_SET:
1972 0 : break;
1973 : case SEEK_CUR:
1974 0 : aOffset += mOffset;
1975 0 : break;
1976 : case SEEK_END:
1977 : {
1978 0 : int64_t length = mResource->GetLength();
1979 0 : if (length == -1 || length - aOffset < 0) {
1980 0 : return NS_ERROR_FAILURE;
1981 : }
1982 0 : aOffset = mResource->GetLength() - aOffset;
1983 : }
1984 0 : break;
1985 : default:
1986 0 : return NS_ERROR_FAILURE;
1987 : }
1988 :
1989 0 : mOffset = aOffset;
1990 :
1991 0 : return NS_OK;
1992 : }
1993 :
1994 : } // namespace mozilla
1995 :
1996 : // avoid redefined macro in unified build
1997 : #undef LOG
1998 : #undef ILOG
|