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 : /* ***** BEGIN LICENSE BLOCK *****
4 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 : *
6 : * The contents of this file are subject to the Mozilla Public License Version
7 : * 1.1 (the "License"); you may not use this file except in compliance with
8 : * the License. You may obtain a copy of the License at
9 : * http://www.mozilla.org/MPL/
10 : *
11 : * Software distributed under the License is distributed on an "AS IS" basis,
12 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 : * for the specific language governing rights and limitations under the
14 : * License.
15 : *
16 : * The Original Code is Mozilla code.
17 : *
18 : * The Initial Developer of the Original Code is the Mozilla Corporation.
19 : * Portions created by the Initial Developer are Copyright (C) 2007
20 : * the Initial Developer. All Rights Reserved.
21 : *
22 : * Contributor(s):
23 : * Chris Double <chris.double@double.co.nz>
24 : *
25 : * Alternatively, the contents of this file may be used under the terms of
26 : * either the GNU General Public License Version 2 or later (the "GPL"), or
27 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 : * in which case the provisions of the GPL or the LGPL are applicable instead
29 : * of those above. If you wish to allow use of your version of this file only
30 : * under the terms of either the GPL or the LGPL, and not to allow others to
31 : * use your version of this file under the terms of the MPL, indicate your
32 : * decision by deleting the provisions above and replace them with the notice
33 : * and other provisions required by the GPL or the LGPL. If you do not delete
34 : * the provisions above, a recipient may use your version of this file under
35 : * the terms of any one of the MPL, the GPL or the LGPL.
36 : *
37 : * ***** END LICENSE BLOCK ***** */
38 :
39 : #include "MediaResource.h"
40 :
41 : #include "mozilla/Mutex.h"
42 : #include "nsDebug.h"
43 : #include "nsMediaDecoder.h"
44 : #include "nsNetUtil.h"
45 : #include "nsThreadUtils.h"
46 : #include "nsIFile.h"
47 : #include "nsIFileChannel.h"
48 : #include "nsIHttpChannel.h"
49 : #include "nsISeekableStream.h"
50 : #include "nsIInputStream.h"
51 : #include "nsIOutputStream.h"
52 : #include "nsIRequestObserver.h"
53 : #include "nsIStreamListener.h"
54 : #include "nsIScriptSecurityManager.h"
55 : #include "nsCrossSiteListenerProxy.h"
56 : #include "nsHTMLMediaElement.h"
57 : #include "nsIDocument.h"
58 : #include "nsDOMError.h"
59 : #include "nsICachingChannel.h"
60 : #include "nsURILoader.h"
61 : #include "nsIAsyncVerifyRedirectCallback.h"
62 : #include "mozilla/Util.h" // for DebugOnly
63 : #include "nsContentUtils.h"
64 :
65 : static const PRUint32 HTTP_OK_CODE = 200;
66 : static const PRUint32 HTTP_PARTIAL_RESPONSE_CODE = 206;
67 :
68 : using namespace mozilla;
69 :
70 0 : ChannelMediaResource::ChannelMediaResource(nsMediaDecoder* aDecoder,
71 : nsIChannel* aChannel, nsIURI* aURI)
72 : : MediaResource(aDecoder, aChannel, aURI),
73 : mOffset(0), mSuspendCount(0),
74 : mReopenOnError(false), mIgnoreClose(false),
75 : mCacheStream(this),
76 : mLock("ChannelMediaResource.mLock"),
77 0 : mIgnoreResume(false)
78 : {
79 0 : }
80 :
81 0 : ChannelMediaResource::~ChannelMediaResource()
82 : {
83 0 : if (mListener) {
84 : // Kill its reference to us since we're going away
85 0 : mListener->Revoke();
86 : }
87 0 : }
88 :
89 : // ChannelMediaResource::Listener just observes the channel and
90 : // forwards notifications to the ChannelMediaResource. We use multiple
91 : // listener objects so that when we open a new stream for a seek we can
92 : // disconnect the old listener from the ChannelMediaResource and hook up
93 : // a new listener, so notifications from the old channel are discarded
94 : // and don't confuse us.
95 0 : NS_IMPL_ISUPPORTS4(ChannelMediaResource::Listener,
96 : nsIRequestObserver, nsIStreamListener, nsIChannelEventSink,
97 : nsIInterfaceRequestor)
98 :
99 : nsresult
100 0 : ChannelMediaResource::Listener::OnStartRequest(nsIRequest* aRequest,
101 : nsISupports* aContext)
102 : {
103 0 : if (!mResource)
104 0 : return NS_OK;
105 0 : return mResource->OnStartRequest(aRequest);
106 : }
107 :
108 : nsresult
109 0 : ChannelMediaResource::Listener::OnStopRequest(nsIRequest* aRequest,
110 : nsISupports* aContext,
111 : nsresult aStatus)
112 : {
113 0 : if (!mResource)
114 0 : return NS_OK;
115 0 : return mResource->OnStopRequest(aRequest, aStatus);
116 : }
117 :
118 : nsresult
119 0 : ChannelMediaResource::Listener::OnDataAvailable(nsIRequest* aRequest,
120 : nsISupports* aContext,
121 : nsIInputStream* aStream,
122 : PRUint32 aOffset,
123 : PRUint32 aCount)
124 : {
125 0 : if (!mResource)
126 0 : return NS_OK;
127 0 : return mResource->OnDataAvailable(aRequest, aStream, aCount);
128 : }
129 :
130 : nsresult
131 0 : ChannelMediaResource::Listener::AsyncOnChannelRedirect(nsIChannel* aOldChannel,
132 : nsIChannel* aNewChannel,
133 : PRUint32 aFlags,
134 : nsIAsyncVerifyRedirectCallback* cb)
135 : {
136 0 : nsresult rv = NS_OK;
137 0 : if (mResource)
138 0 : rv = mResource->OnChannelRedirect(aOldChannel, aNewChannel, aFlags);
139 :
140 0 : if (NS_FAILED(rv))
141 0 : return rv;
142 :
143 0 : cb->OnRedirectVerifyCallback(NS_OK);
144 0 : return NS_OK;
145 : }
146 :
147 : nsresult
148 0 : ChannelMediaResource::Listener::GetInterface(const nsIID & aIID, void **aResult)
149 : {
150 0 : return QueryInterface(aIID, aResult);
151 : }
152 :
153 : nsresult
154 0 : ChannelMediaResource::OnStartRequest(nsIRequest* aRequest)
155 : {
156 0 : NS_ASSERTION(mChannel.get() == aRequest, "Wrong channel!");
157 :
158 0 : nsHTMLMediaElement* element = mDecoder->GetMediaElement();
159 0 : NS_ENSURE_TRUE(element, NS_ERROR_FAILURE);
160 : nsresult status;
161 0 : nsresult rv = aRequest->GetStatus(&status);
162 0 : NS_ENSURE_SUCCESS(rv, rv);
163 :
164 0 : if (element->ShouldCheckAllowOrigin()) {
165 : // If the request was cancelled by nsCORSListenerProxy due to failing
166 : // the CORS security check, send an error through to the media element.
167 0 : if (status == NS_ERROR_DOM_BAD_URI) {
168 0 : mDecoder->NetworkError();
169 0 : return NS_ERROR_DOM_BAD_URI;
170 : }
171 : }
172 :
173 0 : nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(aRequest);
174 0 : bool seekable = false;
175 0 : if (hc) {
176 0 : PRUint32 responseStatus = 0;
177 0 : hc->GetResponseStatus(&responseStatus);
178 0 : bool succeeded = false;
179 0 : hc->GetRequestSucceeded(&succeeded);
180 :
181 0 : if (!succeeded && NS_SUCCEEDED(status)) {
182 : // HTTP-level error (e.g. 4xx); treat this as a fatal network-level error.
183 : // We might get this on a seek.
184 : // (Note that lower-level errors indicated by NS_FAILED(status) are
185 : // handled in OnStopRequest.)
186 : // A 416 error should treated as EOF here... it's possible
187 : // that we don't get Content-Length, we read N bytes, then we
188 : // suspend and resume, the resume reopens the channel and we seek to
189 : // offset N, but there are no more bytes, so we get a 416
190 : // "Requested Range Not Satisfiable".
191 0 : if (responseStatus != HTTP_REQUESTED_RANGE_NOT_SATISFIABLE_CODE) {
192 0 : mDecoder->NetworkError();
193 : }
194 :
195 : // This disconnects our listener so we don't get any more data. We
196 : // certainly don't want an error page to end up in our cache!
197 0 : CloseChannel();
198 0 : return NS_OK;
199 : }
200 :
201 0 : nsCAutoString ranges;
202 0 : hc->GetResponseHeader(NS_LITERAL_CSTRING("Accept-Ranges"),
203 0 : ranges);
204 0 : bool acceptsRanges = ranges.EqualsLiteral("bytes");
205 :
206 0 : if (mOffset == 0) {
207 : // Look for duration headers from known Ogg content systems.
208 : // In the case of multiple options for obtaining the duration
209 : // the order of precedence is:
210 : // 1) The Media resource metadata if possible (done by the decoder itself).
211 : // 2) Content-Duration message header.
212 : // 3) X-AMZ-Meta-Content-Duration.
213 : // 4) X-Content-Duration.
214 : // 5) Perform a seek in the decoder to find the value.
215 0 : nsCAutoString durationText;
216 0 : PRInt32 ec = 0;
217 0 : rv = hc->GetResponseHeader(NS_LITERAL_CSTRING("Content-Duration"), durationText);
218 0 : if (NS_FAILED(rv)) {
219 0 : rv = hc->GetResponseHeader(NS_LITERAL_CSTRING("X-AMZ-Meta-Content-Duration"), durationText);
220 : }
221 0 : if (NS_FAILED(rv)) {
222 0 : rv = hc->GetResponseHeader(NS_LITERAL_CSTRING("X-Content-Duration"), durationText);
223 : }
224 :
225 0 : if (NS_SUCCEEDED(rv)) {
226 0 : double duration = durationText.ToDouble(&ec);
227 0 : if (ec == NS_OK && duration >= 0) {
228 0 : mDecoder->SetDuration(duration);
229 : }
230 : } else {
231 0 : mDecoder->SetInfinite(true);
232 : }
233 : }
234 :
235 0 : if (mOffset > 0 && responseStatus == HTTP_OK_CODE) {
236 : // If we get an OK response but we were seeking, we have to assume
237 : // that seeking doesn't work. We also need to tell the cache that
238 : // it's getting data for the start of the stream.
239 0 : mCacheStream.NotifyDataStarted(0);
240 0 : mOffset = 0;
241 :
242 : // The server claimed it supported range requests. It lied.
243 0 : acceptsRanges = false;
244 0 : } else if (mOffset == 0 &&
245 : (responseStatus == HTTP_OK_CODE ||
246 : responseStatus == HTTP_PARTIAL_RESPONSE_CODE)) {
247 : // We weren't seeking and got a valid response status,
248 : // set the length of the content.
249 0 : PRInt32 cl = -1;
250 0 : hc->GetContentLength(&cl);
251 0 : if (cl >= 0) {
252 0 : mCacheStream.NotifyDataLength(cl);
253 : }
254 : }
255 : // XXX we probably should examine the Content-Range header in case
256 : // the server gave us a range which is not quite what we asked for
257 :
258 : // If we get an HTTP_OK_CODE response to our byte range request,
259 : // and the server isn't sending Accept-Ranges:bytes then we don't
260 : // support seeking.
261 : seekable =
262 0 : responseStatus == HTTP_PARTIAL_RESPONSE_CODE || acceptsRanges;
263 :
264 0 : if (seekable) {
265 0 : mDecoder->SetInfinite(false);
266 : }
267 : }
268 0 : mDecoder->SetSeekable(seekable);
269 0 : mCacheStream.SetSeekable(seekable);
270 :
271 0 : nsCOMPtr<nsICachingChannel> cc = do_QueryInterface(aRequest);
272 0 : if (cc) {
273 0 : bool fromCache = false;
274 0 : rv = cc->IsFromCache(&fromCache);
275 0 : if (NS_SUCCEEDED(rv) && !fromCache) {
276 0 : cc->SetCacheAsFile(true);
277 : }
278 : }
279 :
280 : {
281 0 : MutexAutoLock lock(mLock);
282 0 : mChannelStatistics.Start(TimeStamp::Now());
283 : }
284 :
285 0 : mReopenOnError = false;
286 0 : mIgnoreClose = false;
287 0 : if (mSuspendCount > 0) {
288 : // Re-suspend the channel if it needs to be suspended
289 : // No need to call PossiblySuspend here since the channel is
290 : // definitely in the right state for us in OnStartRequest.
291 0 : mChannel->Suspend();
292 0 : mIgnoreResume = false;
293 : }
294 :
295 : // Fires an initial progress event and sets up the stall counter so stall events
296 : // fire if no download occurs within the required time frame.
297 0 : mDecoder->Progress(false);
298 :
299 0 : return NS_OK;
300 : }
301 :
302 : nsresult
303 0 : ChannelMediaResource::OnStopRequest(nsIRequest* aRequest, nsresult aStatus)
304 : {
305 0 : NS_ASSERTION(mChannel.get() == aRequest, "Wrong channel!");
306 0 : NS_ASSERTION(mSuspendCount == 0,
307 : "How can OnStopRequest fire while we're suspended?");
308 :
309 : {
310 0 : MutexAutoLock lock(mLock);
311 0 : mChannelStatistics.Stop(TimeStamp::Now());
312 : }
313 :
314 : // Note that aStatus might have succeeded --- this might be a normal close
315 : // --- even in situations where the server cut us off because we were
316 : // suspended. So we need to "reopen on error" in that case too. The only
317 : // cases where we don't need to reopen are when *we* closed the stream.
318 : // But don't reopen if we need to seek and we don't think we can... that would
319 : // cause us to just re-read the stream, which would be really bad.
320 0 : if (mReopenOnError &&
321 : aStatus != NS_ERROR_PARSED_DATA_CACHED && aStatus != NS_BINDING_ABORTED &&
322 0 : (mOffset == 0 || mCacheStream.IsSeekable())) {
323 : // If the stream did close normally, then if the server is seekable we'll
324 : // just seek to the end of the resource and get an HTTP 416 error because
325 : // there's nothing there, so this isn't bad.
326 0 : nsresult rv = CacheClientSeek(mOffset, false);
327 0 : if (NS_SUCCEEDED(rv))
328 0 : return rv;
329 : // If the reopen/reseek fails, just fall through and treat this
330 : // error as fatal.
331 : }
332 :
333 0 : if (!mIgnoreClose) {
334 0 : mCacheStream.NotifyDataEnded(aStatus);
335 :
336 : // Move this request back into the foreground. This is necessary for
337 : // requests owned by video documents to ensure the load group fires
338 : // OnStopRequest when restoring from session history.
339 : nsLoadFlags loadFlags;
340 0 : DebugOnly<nsresult> rv = mChannel->GetLoadFlags(&loadFlags);
341 0 : NS_ASSERTION(NS_SUCCEEDED(rv), "GetLoadFlags() failed!");
342 :
343 0 : if (loadFlags & nsIRequest::LOAD_BACKGROUND) {
344 0 : ModifyLoadFlags(loadFlags & ~nsIRequest::LOAD_BACKGROUND);
345 : }
346 : }
347 :
348 0 : return NS_OK;
349 : }
350 :
351 : nsresult
352 0 : ChannelMediaResource::OnChannelRedirect(nsIChannel* aOld, nsIChannel* aNew,
353 : PRUint32 aFlags)
354 : {
355 0 : mChannel = aNew;
356 0 : SetupChannelHeaders();
357 0 : return NS_OK;
358 : }
359 :
360 0 : struct CopySegmentClosure {
361 : nsCOMPtr<nsIPrincipal> mPrincipal;
362 : ChannelMediaResource* mResource;
363 : };
364 :
365 : NS_METHOD
366 0 : ChannelMediaResource::CopySegmentToCache(nsIInputStream *aInStream,
367 : void *aClosure,
368 : const char *aFromSegment,
369 : PRUint32 aToOffset,
370 : PRUint32 aCount,
371 : PRUint32 *aWriteCount)
372 : {
373 0 : CopySegmentClosure* closure = static_cast<CopySegmentClosure*>(aClosure);
374 :
375 0 : closure->mResource->mDecoder->NotifyDataArrived(aFromSegment, aCount, closure->mResource->mOffset);
376 :
377 : // Keep track of where we're up to
378 0 : closure->mResource->mOffset += aCount;
379 : closure->mResource->mCacheStream.NotifyDataReceived(aCount, aFromSegment,
380 0 : closure->mPrincipal);
381 0 : *aWriteCount = aCount;
382 0 : return NS_OK;
383 : }
384 :
385 : nsresult
386 0 : ChannelMediaResource::OnDataAvailable(nsIRequest* aRequest,
387 : nsIInputStream* aStream,
388 : PRUint32 aCount)
389 : {
390 0 : NS_ASSERTION(mChannel.get() == aRequest, "Wrong channel!");
391 :
392 : {
393 0 : MutexAutoLock lock(mLock);
394 0 : mChannelStatistics.AddBytes(aCount);
395 : }
396 :
397 0 : CopySegmentClosure closure;
398 0 : nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
399 0 : if (secMan && mChannel) {
400 0 : secMan->GetChannelPrincipal(mChannel, getter_AddRefs(closure.mPrincipal));
401 : }
402 0 : closure.mResource = this;
403 :
404 0 : PRUint32 count = aCount;
405 0 : while (count > 0) {
406 : PRUint32 read;
407 : nsresult rv = aStream->ReadSegments(CopySegmentToCache, &closure, count,
408 0 : &read);
409 0 : if (NS_FAILED(rv))
410 0 : return rv;
411 0 : NS_ASSERTION(read > 0, "Read 0 bytes while data was available?");
412 0 : count -= read;
413 : }
414 :
415 0 : return NS_OK;
416 : }
417 :
418 0 : nsresult ChannelMediaResource::Open(nsIStreamListener **aStreamListener)
419 : {
420 0 : NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
421 :
422 0 : nsresult rv = mCacheStream.Init();
423 0 : if (NS_FAILED(rv))
424 0 : return rv;
425 0 : NS_ASSERTION(mOffset == 0, "Who set mOffset already?");
426 :
427 0 : if (!mChannel) {
428 : // When we're a clone, the decoder might ask us to Open even though
429 : // we haven't established an mChannel (because we might not need one)
430 0 : NS_ASSERTION(!aStreamListener,
431 : "Should have already been given a channel if we're to return a stream listener");
432 0 : return NS_OK;
433 : }
434 :
435 0 : return OpenChannel(aStreamListener);
436 : }
437 :
438 0 : nsresult ChannelMediaResource::OpenChannel(nsIStreamListener** aStreamListener)
439 : {
440 0 : NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
441 0 : NS_ENSURE_TRUE(mChannel, NS_ERROR_NULL_POINTER);
442 0 : NS_ASSERTION(!mListener, "Listener should have been removed by now");
443 :
444 0 : if (aStreamListener) {
445 0 : *aStreamListener = nsnull;
446 : }
447 :
448 0 : mListener = new Listener(this);
449 0 : NS_ENSURE_TRUE(mListener, NS_ERROR_OUT_OF_MEMORY);
450 :
451 0 : if (aStreamListener) {
452 0 : *aStreamListener = mListener;
453 0 : NS_ADDREF(*aStreamListener);
454 : } else {
455 0 : mChannel->SetNotificationCallbacks(mListener.get());
456 :
457 0 : nsCOMPtr<nsIStreamListener> listener = mListener.get();
458 :
459 : // Ensure that if we're loading cross domain, that the server is sending
460 : // an authorizing Access-Control header.
461 0 : nsHTMLMediaElement* element = mDecoder->GetMediaElement();
462 0 : NS_ENSURE_TRUE(element, NS_ERROR_FAILURE);
463 0 : if (element->ShouldCheckAllowOrigin()) {
464 : nsresult rv;
465 : nsCORSListenerProxy* crossSiteListener =
466 : new nsCORSListenerProxy(mListener,
467 0 : element->NodePrincipal(),
468 : mChannel,
469 : false,
470 0 : &rv);
471 0 : listener = crossSiteListener;
472 0 : NS_ENSURE_TRUE(crossSiteListener, NS_ERROR_OUT_OF_MEMORY);
473 0 : NS_ENSURE_SUCCESS(rv, rv);
474 : } else {
475 0 : nsresult rv = nsContentUtils::GetSecurityManager()->
476 : CheckLoadURIWithPrincipal(element->NodePrincipal(),
477 : mURI,
478 0 : nsIScriptSecurityManager::STANDARD);
479 0 : NS_ENSURE_SUCCESS(rv, rv);
480 : }
481 :
482 0 : SetupChannelHeaders();
483 :
484 0 : nsresult rv = mChannel->AsyncOpen(listener, nsnull);
485 0 : NS_ENSURE_SUCCESS(rv, rv);
486 : }
487 :
488 0 : return NS_OK;
489 : }
490 :
491 0 : void ChannelMediaResource::SetupChannelHeaders()
492 : {
493 : // Always use a byte range request even if we're reading from the start
494 : // of the resource.
495 : // This enables us to detect if the stream supports byte range
496 : // requests, and therefore seeking, early.
497 0 : nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(mChannel);
498 0 : if (hc) {
499 0 : nsCAutoString rangeString("bytes=");
500 0 : rangeString.AppendInt(mOffset);
501 0 : rangeString.Append("-");
502 0 : hc->SetRequestHeader(NS_LITERAL_CSTRING("Range"), rangeString, false);
503 :
504 : // Send Accept header for video and audio types only (Bug 489071)
505 0 : NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread");
506 0 : nsHTMLMediaElement* element = mDecoder->GetMediaElement();
507 0 : if (!element) {
508 : return;
509 : }
510 0 : element->SetRequestHeaders(hc);
511 : } else {
512 0 : NS_ASSERTION(mOffset == 0, "Don't know how to seek on this channel type");
513 : }
514 : }
515 :
516 0 : nsresult ChannelMediaResource::Close()
517 : {
518 0 : NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
519 :
520 0 : mCacheStream.Close();
521 0 : CloseChannel();
522 0 : return NS_OK;
523 : }
524 :
525 0 : already_AddRefed<nsIPrincipal> ChannelMediaResource::GetCurrentPrincipal()
526 : {
527 0 : NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
528 :
529 0 : nsCOMPtr<nsIPrincipal> principal = mCacheStream.GetCurrentPrincipal();
530 0 : return principal.forget();
531 : }
532 :
533 0 : bool ChannelMediaResource::CanClone()
534 : {
535 0 : return mCacheStream.IsAvailableForSharing();
536 : }
537 :
538 0 : MediaResource* ChannelMediaResource::CloneData(nsMediaDecoder* aDecoder)
539 : {
540 0 : NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
541 0 : NS_ASSERTION(mCacheStream.IsAvailableForSharing(), "Stream can't be cloned");
542 :
543 0 : ChannelMediaResource* resource = new ChannelMediaResource(aDecoder, nsnull, mURI);
544 0 : if (resource) {
545 : // Initially the clone is treated as suspended by the cache, because
546 : // we don't have a channel. If the cache needs to read data from the clone
547 : // it will call CacheClientResume (or CacheClientSeek with aResume true)
548 : // which will recreate the channel. This way, if all of the media data
549 : // is already in the cache we don't create an unneccesary HTTP channel
550 : // and perform a useless HTTP transaction.
551 0 : resource->mSuspendCount = 1;
552 0 : resource->mCacheStream.InitAsClone(&mCacheStream);
553 0 : resource->mChannelStatistics = mChannelStatistics;
554 0 : resource->mChannelStatistics.Stop(TimeStamp::Now());
555 : }
556 0 : return resource;
557 : }
558 :
559 0 : void ChannelMediaResource::CloseChannel()
560 : {
561 0 : NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
562 :
563 : {
564 0 : MutexAutoLock lock(mLock);
565 0 : mChannelStatistics.Stop(TimeStamp::Now());
566 : }
567 :
568 0 : if (mListener) {
569 0 : mListener->Revoke();
570 0 : mListener = nsnull;
571 : }
572 :
573 0 : if (mChannel) {
574 0 : if (mSuspendCount > 0) {
575 : // Resume the channel before we cancel it
576 0 : PossiblyResume();
577 : }
578 : // The status we use here won't be passed to the decoder, since
579 : // we've already revoked the listener. It can however be passed
580 : // to DocumentViewerImpl::LoadComplete if our channel is the one
581 : // that kicked off creation of a video document. We don't want that
582 : // document load to think there was an error.
583 : // NS_ERROR_PARSED_DATA_CACHED is the best thing we have for that
584 : // at the moment.
585 0 : mChannel->Cancel(NS_ERROR_PARSED_DATA_CACHED);
586 0 : mChannel = nsnull;
587 : }
588 0 : }
589 :
590 0 : nsresult ChannelMediaResource::ReadFromCache(char* aBuffer,
591 : PRInt64 aOffset,
592 : PRUint32 aCount)
593 : {
594 0 : return mCacheStream.ReadFromCache(aBuffer, aOffset, aCount);
595 : }
596 :
597 0 : nsresult ChannelMediaResource::Read(char* aBuffer,
598 : PRUint32 aCount,
599 : PRUint32* aBytes)
600 : {
601 0 : NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
602 :
603 0 : return mCacheStream.Read(aBuffer, aCount, aBytes);
604 : }
605 :
606 0 : nsresult ChannelMediaResource::Seek(PRInt32 aWhence, PRInt64 aOffset)
607 : {
608 0 : NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
609 :
610 0 : return mCacheStream.Seek(aWhence, aOffset);
611 : }
612 :
613 0 : PRInt64 ChannelMediaResource::Tell()
614 : {
615 0 : NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
616 :
617 0 : return mCacheStream.Tell();
618 : }
619 :
620 0 : nsresult ChannelMediaResource::GetCachedRanges(nsTArray<MediaByteRange>& aRanges)
621 : {
622 0 : return mCacheStream.GetCachedRanges(aRanges);
623 : }
624 :
625 0 : void ChannelMediaResource::Suspend(bool aCloseImmediately)
626 : {
627 0 : NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread");
628 :
629 0 : nsHTMLMediaElement* element = mDecoder->GetMediaElement();
630 0 : if (!element) {
631 : // Shutting down; do nothing.
632 0 : return;
633 : }
634 :
635 0 : if (mChannel) {
636 0 : if (aCloseImmediately && mCacheStream.IsSeekable()) {
637 : // Kill off our channel right now, but don't tell anyone about it.
638 0 : mIgnoreClose = true;
639 0 : CloseChannel();
640 0 : element->DownloadSuspended();
641 0 : } else if (mSuspendCount == 0) {
642 : {
643 0 : MutexAutoLock lock(mLock);
644 0 : mChannelStatistics.Stop(TimeStamp::Now());
645 : }
646 0 : PossiblySuspend();
647 0 : element->DownloadSuspended();
648 : }
649 : }
650 :
651 0 : ++mSuspendCount;
652 : }
653 :
654 0 : void ChannelMediaResource::Resume()
655 : {
656 0 : NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread");
657 0 : NS_ASSERTION(mSuspendCount > 0, "Too many resumes!");
658 :
659 0 : nsHTMLMediaElement* element = mDecoder->GetMediaElement();
660 0 : if (!element) {
661 : // Shutting down; do nothing.
662 0 : return;
663 : }
664 :
665 0 : NS_ASSERTION(mSuspendCount > 0, "Resume without previous Suspend!");
666 0 : --mSuspendCount;
667 0 : if (mSuspendCount == 0) {
668 0 : if (mChannel) {
669 : // Just wake up our existing channel
670 : {
671 0 : MutexAutoLock lock(mLock);
672 0 : mChannelStatistics.Start(TimeStamp::Now());
673 : }
674 : // if an error occurs after Resume, assume it's because the server
675 : // timed out the connection and we should reopen it.
676 0 : mReopenOnError = true;
677 0 : PossiblyResume();
678 0 : element->DownloadResumed();
679 : } else {
680 0 : PRInt64 totalLength = mCacheStream.GetLength();
681 : // If mOffset is at the end of the stream, then we shouldn't try to
682 : // seek to it. The seek will fail and be wasted anyway. We can leave
683 : // the channel dead; if the media cache wants to read some other data
684 : // in the future, it will call CacheClientSeek itself which will reopen the
685 : // channel.
686 0 : if (totalLength < 0 || mOffset < totalLength) {
687 : // There is (or may be) data to read at mOffset, so start reading it.
688 : // Need to recreate the channel.
689 0 : CacheClientSeek(mOffset, false);
690 : }
691 0 : element->DownloadResumed();
692 : }
693 : }
694 : }
695 :
696 : nsresult
697 0 : ChannelMediaResource::RecreateChannel()
698 : {
699 : nsLoadFlags loadFlags =
700 : nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE_IF_BUSY |
701 0 : (mLoadInBackground ? nsIRequest::LOAD_BACKGROUND : 0);
702 :
703 0 : nsHTMLMediaElement* element = mDecoder->GetMediaElement();
704 0 : if (!element) {
705 : // The decoder is being shut down, so don't bother opening a new channel
706 0 : return NS_OK;
707 : }
708 0 : nsCOMPtr<nsILoadGroup> loadGroup = element->GetDocumentLoadGroup();
709 0 : NS_ENSURE_TRUE(loadGroup, NS_ERROR_NULL_POINTER);
710 :
711 0 : return NS_NewChannel(getter_AddRefs(mChannel),
712 : mURI,
713 : nsnull,
714 : loadGroup,
715 : nsnull,
716 0 : loadFlags);
717 : }
718 :
719 : void
720 0 : ChannelMediaResource::DoNotifyDataReceived()
721 : {
722 0 : mDataReceivedEvent.Revoke();
723 0 : mDecoder->NotifyBytesDownloaded();
724 0 : }
725 :
726 : void
727 0 : ChannelMediaResource::CacheClientNotifyDataReceived()
728 : {
729 0 : NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread");
730 : // NOTE: this can be called with the media cache lock held, so don't
731 : // block or do anything which might try to acquire a lock!
732 :
733 0 : if (mDataReceivedEvent.IsPending())
734 0 : return;
735 :
736 : mDataReceivedEvent =
737 0 : NS_NewNonOwningRunnableMethod(this, &ChannelMediaResource::DoNotifyDataReceived);
738 0 : NS_DispatchToMainThread(mDataReceivedEvent.get(), NS_DISPATCH_NORMAL);
739 : }
740 :
741 0 : class DataEnded : public nsRunnable {
742 : public:
743 0 : DataEnded(nsMediaDecoder* aDecoder, nsresult aStatus) :
744 0 : mDecoder(aDecoder), mStatus(aStatus) {}
745 0 : NS_IMETHOD Run() {
746 0 : mDecoder->NotifyDownloadEnded(mStatus);
747 0 : return NS_OK;
748 : }
749 : private:
750 : nsRefPtr<nsMediaDecoder> mDecoder;
751 : nsresult mStatus;
752 : };
753 :
754 : void
755 0 : ChannelMediaResource::CacheClientNotifyDataEnded(nsresult aStatus)
756 : {
757 0 : NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread");
758 : // NOTE: this can be called with the media cache lock held, so don't
759 : // block or do anything which might try to acquire a lock!
760 :
761 0 : nsCOMPtr<nsIRunnable> event = new DataEnded(mDecoder, aStatus);
762 0 : NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
763 0 : }
764 :
765 : nsresult
766 0 : ChannelMediaResource::CacheClientSeek(PRInt64 aOffset, bool aResume)
767 : {
768 0 : NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread");
769 :
770 0 : CloseChannel();
771 :
772 0 : if (aResume) {
773 0 : NS_ASSERTION(mSuspendCount > 0, "Too many resumes!");
774 : // No need to mess with the channel, since we're making a new one
775 0 : --mSuspendCount;
776 : }
777 :
778 0 : nsresult rv = RecreateChannel();
779 0 : if (NS_FAILED(rv))
780 0 : return rv;
781 :
782 0 : mOffset = aOffset;
783 0 : return OpenChannel(nsnull);
784 : }
785 :
786 : nsresult
787 0 : ChannelMediaResource::CacheClientSuspend()
788 : {
789 0 : Suspend(false);
790 :
791 0 : mDecoder->NotifySuspendedStatusChanged();
792 0 : return NS_OK;
793 : }
794 :
795 : nsresult
796 0 : ChannelMediaResource::CacheClientResume()
797 : {
798 0 : Resume();
799 :
800 0 : mDecoder->NotifySuspendedStatusChanged();
801 0 : return NS_OK;
802 : }
803 :
804 : PRInt64
805 0 : ChannelMediaResource::GetNextCachedData(PRInt64 aOffset)
806 : {
807 0 : return mCacheStream.GetNextCachedData(aOffset);
808 : }
809 :
810 : PRInt64
811 0 : ChannelMediaResource::GetCachedDataEnd(PRInt64 aOffset)
812 : {
813 0 : return mCacheStream.GetCachedDataEnd(aOffset);
814 : }
815 :
816 : bool
817 0 : ChannelMediaResource::IsDataCachedToEndOfResource(PRInt64 aOffset)
818 : {
819 0 : return mCacheStream.IsDataCachedToEndOfStream(aOffset);
820 : }
821 :
822 : void
823 0 : ChannelMediaResource::EnsureCacheUpToDate()
824 : {
825 0 : mCacheStream.EnsureCacheUpdate();
826 0 : }
827 :
828 : bool
829 0 : ChannelMediaResource::IsSuspendedByCache(MediaResource** aActiveResource)
830 : {
831 0 : return mCacheStream.AreAllStreamsForResourceSuspended(aActiveResource);
832 : }
833 :
834 : bool
835 0 : ChannelMediaResource::IsSuspended()
836 : {
837 0 : MutexAutoLock lock(mLock);
838 0 : return mSuspendCount > 0;
839 : }
840 :
841 : void
842 0 : ChannelMediaResource::SetReadMode(nsMediaCacheStream::ReadMode aMode)
843 : {
844 0 : mCacheStream.SetReadMode(aMode);
845 0 : }
846 :
847 : void
848 0 : ChannelMediaResource::SetPlaybackRate(PRUint32 aBytesPerSecond)
849 : {
850 0 : mCacheStream.SetPlaybackRate(aBytesPerSecond);
851 0 : }
852 :
853 : void
854 0 : ChannelMediaResource::Pin()
855 : {
856 0 : mCacheStream.Pin();
857 0 : }
858 :
859 : void
860 0 : ChannelMediaResource::Unpin()
861 : {
862 0 : mCacheStream.Unpin();
863 0 : }
864 :
865 : double
866 0 : ChannelMediaResource::GetDownloadRate(bool* aIsReliable)
867 : {
868 0 : MutexAutoLock lock(mLock);
869 0 : return mChannelStatistics.GetRate(TimeStamp::Now(), aIsReliable);
870 : }
871 :
872 : PRInt64
873 0 : ChannelMediaResource::GetLength()
874 : {
875 0 : return mCacheStream.GetLength();
876 : }
877 :
878 : void
879 0 : ChannelMediaResource::PossiblySuspend()
880 : {
881 0 : bool isPending = false;
882 0 : nsresult rv = mChannel->IsPending(&isPending);
883 0 : if (NS_SUCCEEDED(rv) && isPending) {
884 0 : mChannel->Suspend();
885 0 : mIgnoreResume = false;
886 : } else {
887 0 : mIgnoreResume = true;
888 : }
889 0 : }
890 :
891 : void
892 0 : ChannelMediaResource::PossiblyResume()
893 : {
894 0 : if (!mIgnoreResume) {
895 0 : mChannel->Resume();
896 : } else {
897 0 : mIgnoreResume = false;
898 : }
899 0 : }
900 :
901 : class FileMediaResource : public MediaResource
902 : {
903 : public:
904 0 : FileMediaResource(nsMediaDecoder* aDecoder, nsIChannel* aChannel, nsIURI* aURI) :
905 : MediaResource(aDecoder, aChannel, aURI), mSize(-1),
906 0 : mLock("FileMediaResource.mLock")
907 : {
908 0 : }
909 0 : ~FileMediaResource()
910 0 : {
911 0 : }
912 :
913 : // Main thread
914 : virtual nsresult Open(nsIStreamListener** aStreamListener);
915 : virtual nsresult Close();
916 0 : virtual void Suspend(bool aCloseImmediately) {}
917 0 : virtual void Resume() {}
918 : virtual already_AddRefed<nsIPrincipal> GetCurrentPrincipal();
919 : virtual bool CanClone();
920 : virtual MediaResource* CloneData(nsMediaDecoder* aDecoder);
921 : virtual nsresult ReadFromCache(char* aBuffer, PRInt64 aOffset, PRUint32 aCount);
922 :
923 : // These methods are called off the main thread.
924 :
925 : // Other thread
926 0 : virtual void SetReadMode(nsMediaCacheStream::ReadMode aMode) {}
927 0 : virtual void SetPlaybackRate(PRUint32 aBytesPerSecond) {}
928 : virtual nsresult Read(char* aBuffer, PRUint32 aCount, PRUint32* aBytes);
929 : virtual nsresult Seek(PRInt32 aWhence, PRInt64 aOffset);
930 : virtual PRInt64 Tell();
931 :
932 : // Any thread
933 0 : virtual void Pin() {}
934 0 : virtual void Unpin() {}
935 0 : virtual double GetDownloadRate(bool* aIsReliable)
936 : {
937 : // The data's all already here
938 0 : *aIsReliable = true;
939 0 : return 100*1024*1024; // arbitray, use 100MB/s
940 : }
941 0 : virtual PRInt64 GetLength() { return mSize; }
942 0 : virtual PRInt64 GetNextCachedData(PRInt64 aOffset)
943 : {
944 0 : return (aOffset < mSize) ? aOffset : -1;
945 : }
946 0 : virtual PRInt64 GetCachedDataEnd(PRInt64 aOffset) { return NS_MAX(aOffset, mSize); }
947 0 : virtual bool IsDataCachedToEndOfResource(PRInt64 aOffset) { return true; }
948 0 : virtual bool IsSuspendedByCache(MediaResource** aActiveResource)
949 : {
950 0 : if (aActiveResource) {
951 0 : *aActiveResource = nsnull;
952 : }
953 0 : return false;
954 : }
955 0 : virtual bool IsSuspended() { return false; }
956 :
957 : nsresult GetCachedRanges(nsTArray<MediaByteRange>& aRanges);
958 :
959 : private:
960 : // The file size, or -1 if not known. Immutable after Open().
961 : PRInt64 mSize;
962 :
963 : // This lock handles synchronisation between calls to Close() and
964 : // the Read, Seek, etc calls. Close must not be called while a
965 : // Read or Seek is in progress since it resets various internal
966 : // values to null.
967 : // This lock protects mSeekable and mInput.
968 : Mutex mLock;
969 :
970 : // Seekable stream interface to file. This can be used from any
971 : // thread.
972 : nsCOMPtr<nsISeekableStream> mSeekable;
973 :
974 : // Input stream for the media data. This can be used from any
975 : // thread.
976 : nsCOMPtr<nsIInputStream> mInput;
977 : };
978 :
979 : class LoadedEvent : public nsRunnable
980 : {
981 : public:
982 0 : LoadedEvent(nsMediaDecoder* aDecoder) :
983 0 : mDecoder(aDecoder)
984 : {
985 0 : MOZ_COUNT_CTOR(LoadedEvent);
986 0 : }
987 0 : ~LoadedEvent()
988 0 : {
989 0 : MOZ_COUNT_DTOR(LoadedEvent);
990 0 : }
991 :
992 0 : NS_IMETHOD Run() {
993 0 : mDecoder->NotifyDownloadEnded(NS_OK);
994 0 : return NS_OK;
995 : }
996 :
997 : private:
998 : nsRefPtr<nsMediaDecoder> mDecoder;
999 : };
1000 :
1001 0 : nsresult FileMediaResource::GetCachedRanges(nsTArray<MediaByteRange>& aRanges)
1002 : {
1003 0 : if (mSize == -1) {
1004 0 : return NS_ERROR_FAILURE;
1005 : }
1006 0 : aRanges.AppendElement(MediaByteRange(0, mSize));
1007 0 : return NS_OK;
1008 : }
1009 :
1010 0 : nsresult FileMediaResource::Open(nsIStreamListener** aStreamListener)
1011 : {
1012 0 : NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
1013 :
1014 0 : if (aStreamListener) {
1015 0 : *aStreamListener = nsnull;
1016 : }
1017 :
1018 0 : nsresult rv = NS_OK;
1019 0 : if (aStreamListener) {
1020 : // The channel is already open. We need a synchronous stream that
1021 : // implements nsISeekableStream, so we have to find the underlying
1022 : // file and reopen it
1023 0 : nsCOMPtr<nsIFileChannel> fc(do_QueryInterface(mChannel));
1024 0 : if (!fc)
1025 0 : return NS_ERROR_UNEXPECTED;
1026 :
1027 0 : nsCOMPtr<nsIFile> file;
1028 0 : rv = fc->GetFile(getter_AddRefs(file));
1029 0 : NS_ENSURE_SUCCESS(rv, rv);
1030 :
1031 0 : rv = NS_NewLocalFileInputStream(getter_AddRefs(mInput), file);
1032 : } else {
1033 : // Ensure that we never load a local file from some page on a
1034 : // web server.
1035 0 : nsHTMLMediaElement* element = mDecoder->GetMediaElement();
1036 0 : NS_ENSURE_TRUE(element, NS_ERROR_FAILURE);
1037 :
1038 0 : rv = nsContentUtils::GetSecurityManager()->
1039 : CheckLoadURIWithPrincipal(element->NodePrincipal(),
1040 : mURI,
1041 0 : nsIScriptSecurityManager::STANDARD);
1042 0 : NS_ENSURE_SUCCESS(rv, rv);
1043 :
1044 0 : rv = mChannel->Open(getter_AddRefs(mInput));
1045 : }
1046 0 : NS_ENSURE_SUCCESS(rv, rv);
1047 :
1048 0 : mSeekable = do_QueryInterface(mInput);
1049 0 : if (!mSeekable) {
1050 : // XXX The file may just be a .url or similar
1051 : // shortcut that points to a Web site. We need to fix this by
1052 : // doing an async open and waiting until we locate the real resource,
1053 : // then using that (if it's still a file!).
1054 0 : return NS_ERROR_FAILURE;
1055 : }
1056 :
1057 : // Get the file size and inform the decoder. Only files up to 4GB are
1058 : // supported here.
1059 : PRUint32 size;
1060 0 : rv = mInput->Available(&size);
1061 0 : if (NS_SUCCEEDED(rv)) {
1062 0 : mSize = size;
1063 : }
1064 :
1065 0 : nsCOMPtr<nsIRunnable> event = new LoadedEvent(mDecoder);
1066 0 : NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
1067 0 : return NS_OK;
1068 : }
1069 :
1070 0 : nsresult FileMediaResource::Close()
1071 : {
1072 0 : NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
1073 :
1074 0 : MutexAutoLock lock(mLock);
1075 0 : if (mChannel) {
1076 0 : mChannel->Cancel(NS_ERROR_PARSED_DATA_CACHED);
1077 0 : mChannel = nsnull;
1078 0 : mInput = nsnull;
1079 0 : mSeekable = nsnull;
1080 : }
1081 :
1082 0 : return NS_OK;
1083 : }
1084 :
1085 0 : already_AddRefed<nsIPrincipal> FileMediaResource::GetCurrentPrincipal()
1086 : {
1087 0 : NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
1088 :
1089 0 : nsCOMPtr<nsIPrincipal> principal;
1090 0 : nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
1091 0 : if (!secMan || !mChannel)
1092 0 : return nsnull;
1093 0 : secMan->GetChannelPrincipal(mChannel, getter_AddRefs(principal));
1094 0 : return principal.forget();
1095 : }
1096 :
1097 0 : bool FileMediaResource::CanClone()
1098 : {
1099 0 : return true;
1100 : }
1101 :
1102 0 : MediaResource* FileMediaResource::CloneData(nsMediaDecoder* aDecoder)
1103 : {
1104 0 : NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
1105 :
1106 0 : nsHTMLMediaElement* element = aDecoder->GetMediaElement();
1107 0 : if (!element) {
1108 : // The decoder is being shut down, so we can't clone
1109 0 : return nsnull;
1110 : }
1111 0 : nsCOMPtr<nsILoadGroup> loadGroup = element->GetDocumentLoadGroup();
1112 0 : NS_ENSURE_TRUE(loadGroup, nsnull);
1113 :
1114 0 : nsCOMPtr<nsIChannel> channel;
1115 : nsresult rv =
1116 0 : NS_NewChannel(getter_AddRefs(channel), mURI, nsnull, loadGroup, nsnull, 0);
1117 0 : if (NS_FAILED(rv))
1118 0 : return nsnull;
1119 :
1120 0 : return new FileMediaResource(aDecoder, channel, mURI);
1121 : }
1122 :
1123 0 : nsresult FileMediaResource::ReadFromCache(char* aBuffer, PRInt64 aOffset, PRUint32 aCount)
1124 : {
1125 0 : MutexAutoLock lock(mLock);
1126 0 : if (!mInput || !mSeekable)
1127 0 : return NS_ERROR_FAILURE;
1128 0 : PRInt64 offset = 0;
1129 0 : nsresult res = mSeekable->Tell(&offset);
1130 0 : NS_ENSURE_SUCCESS(res,res);
1131 0 : res = mSeekable->Seek(nsISeekableStream::NS_SEEK_SET, aOffset);
1132 0 : NS_ENSURE_SUCCESS(res,res);
1133 0 : PRUint32 bytesRead = 0;
1134 0 : do {
1135 0 : PRUint32 x = 0;
1136 0 : PRUint32 bytesToRead = aCount - bytesRead;
1137 0 : res = mInput->Read(aBuffer, bytesToRead, &x);
1138 0 : bytesRead += x;
1139 : } while (bytesRead != aCount && res == NS_OK);
1140 :
1141 : // Reset read head to original position so we don't disturb any other
1142 : // reading thread.
1143 0 : nsresult seekres = mSeekable->Seek(nsISeekableStream::NS_SEEK_SET, offset);
1144 :
1145 : // If a read failed in the loop above, we want to return its failure code.
1146 0 : NS_ENSURE_SUCCESS(res,res);
1147 :
1148 : // Else we succeed if the reset-seek succeeds.
1149 0 : return seekres;
1150 : }
1151 :
1152 0 : nsresult FileMediaResource::Read(char* aBuffer, PRUint32 aCount, PRUint32* aBytes)
1153 : {
1154 0 : MutexAutoLock lock(mLock);
1155 0 : if (!mInput)
1156 0 : return NS_ERROR_FAILURE;
1157 0 : return mInput->Read(aBuffer, aCount, aBytes);
1158 : }
1159 :
1160 0 : nsresult FileMediaResource::Seek(PRInt32 aWhence, PRInt64 aOffset)
1161 : {
1162 0 : NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
1163 :
1164 0 : MutexAutoLock lock(mLock);
1165 0 : if (!mSeekable)
1166 0 : return NS_ERROR_FAILURE;
1167 0 : return mSeekable->Seek(aWhence, aOffset);
1168 : }
1169 :
1170 0 : PRInt64 FileMediaResource::Tell()
1171 : {
1172 0 : NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
1173 :
1174 0 : MutexAutoLock lock(mLock);
1175 0 : if (!mSeekable)
1176 0 : return 0;
1177 :
1178 0 : PRInt64 offset = 0;
1179 0 : mSeekable->Tell(&offset);
1180 0 : return offset;
1181 : }
1182 :
1183 : MediaResource*
1184 0 : MediaResource::Create(nsMediaDecoder* aDecoder, nsIChannel* aChannel)
1185 : {
1186 0 : NS_ASSERTION(NS_IsMainThread(),
1187 : "MediaResource::Open called on non-main thread");
1188 :
1189 : // If the channel was redirected, we want the post-redirect URI;
1190 : // but if the URI scheme was expanded, say from chrome: to jar:file:,
1191 : // we want the original URI.
1192 0 : nsCOMPtr<nsIURI> uri;
1193 0 : nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
1194 0 : NS_ENSURE_SUCCESS(rv, nsnull);
1195 :
1196 0 : nsCOMPtr<nsIFileChannel> fc = do_QueryInterface(aChannel);
1197 0 : if (fc) {
1198 0 : return new FileMediaResource(aDecoder, aChannel, uri);
1199 : }
1200 0 : return new ChannelMediaResource(aDecoder, aChannel, uri);
1201 : }
1202 :
1203 0 : void MediaResource::MoveLoadsToBackground() {
1204 0 : NS_ASSERTION(!mLoadInBackground, "Why are you calling this more than once?");
1205 0 : mLoadInBackground = true;
1206 0 : if (!mChannel) {
1207 : // No channel, resource is probably already loaded.
1208 0 : return;
1209 : }
1210 :
1211 : nsresult rv;
1212 0 : nsHTMLMediaElement* element = mDecoder->GetMediaElement();
1213 0 : if (!element) {
1214 0 : NS_WARNING("Null element in MediaResource::MoveLoadsToBackground()");
1215 0 : return;
1216 : }
1217 :
1218 0 : bool isPending = false;
1219 0 : if (NS_SUCCEEDED(mChannel->IsPending(&isPending)) &&
1220 : isPending) {
1221 : nsLoadFlags loadFlags;
1222 0 : rv = mChannel->GetLoadFlags(&loadFlags);
1223 0 : NS_ASSERTION(NS_SUCCEEDED(rv), "GetLoadFlags() failed!");
1224 :
1225 0 : loadFlags |= nsIRequest::LOAD_BACKGROUND;
1226 0 : ModifyLoadFlags(loadFlags);
1227 : }
1228 : }
1229 :
1230 0 : void MediaResource::ModifyLoadFlags(nsLoadFlags aFlags)
1231 : {
1232 0 : nsCOMPtr<nsILoadGroup> loadGroup;
1233 0 : nsresult rv = mChannel->GetLoadGroup(getter_AddRefs(loadGroup));
1234 0 : NS_ASSERTION(NS_SUCCEEDED(rv), "GetLoadGroup() failed!");
1235 :
1236 : nsresult status;
1237 0 : mChannel->GetStatus(&status);
1238 :
1239 : // Note: if (NS_FAILED(status)), the channel won't be in the load group.
1240 0 : if (loadGroup &&
1241 0 : NS_SUCCEEDED(status)) {
1242 0 : rv = loadGroup->RemoveRequest(mChannel, nsnull, status);
1243 0 : NS_ASSERTION(NS_SUCCEEDED(rv), "RemoveRequest() failed!");
1244 : }
1245 :
1246 0 : rv = mChannel->SetLoadFlags(aFlags);
1247 0 : NS_ASSERTION(NS_SUCCEEDED(rv), "SetLoadFlags() failed!");
1248 :
1249 0 : if (loadGroup &&
1250 0 : NS_SUCCEEDED(status)) {
1251 0 : rv = loadGroup->AddRequest(mChannel, nsnull);
1252 0 : NS_ASSERTION(NS_SUCCEEDED(rv), "AddRequest() failed!");
1253 : }
1254 0 : }
|