Source code

Revision control

Other Tools

1
/* vim:set ts=2 sw=2 sts=2 et cindent: */
2
/* This Source Code Form is subject to the terms of the Mozilla Public
3
* License, v. 2.0. If a copy of the MPL was not distributed with this
4
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6
#include "ChannelMediaResource.h"
7
8
#include "mozilla/dom/HTMLMediaElement.h"
9
#include "nsIAsyncVerifyRedirectCallback.h"
10
#include "nsICachingChannel.h"
11
#include "nsIClassOfService.h"
12
#include "nsIInputStream.h"
13
#include "nsIThreadRetargetableRequest.h"
14
#include "nsITimedChannel.h"
15
#include "nsHttp.h"
16
#include "nsNetUtil.h"
17
18
static const uint32_t HTTP_PARTIAL_RESPONSE_CODE = 206;
19
static const uint32_t HTTP_OK_CODE = 200;
20
static const uint32_t HTTP_REQUESTED_RANGE_NOT_SATISFIABLE_CODE = 416;
21
22
mozilla::LazyLogModule gMediaResourceLog("MediaResource");
23
// Debug logging macro with object pointer and class name.
24
#define LOG(msg, ...) \
25
DDMOZ_LOG(gMediaResourceLog, mozilla::LogLevel::Debug, msg, ##__VA_ARGS__)
26
27
namespace mozilla {
28
29
ChannelMediaResource::ChannelMediaResource(MediaResourceCallback* aCallback,
30
nsIChannel* aChannel, nsIURI* aURI,
31
int64_t aStreamLength,
32
bool aIsPrivateBrowsing)
33
: BaseMediaResource(aCallback, aChannel, aURI),
34
mCacheStream(this, aIsPrivateBrowsing),
35
mSuspendAgent(mCacheStream),
36
mKnownStreamLength(aStreamLength) {}
37
38
ChannelMediaResource::~ChannelMediaResource() {
39
MOZ_ASSERT(mClosed);
40
MOZ_ASSERT(!mChannel);
41
MOZ_ASSERT(!mListener);
42
if (mSharedInfo) {
43
mSharedInfo->mResources.RemoveElement(this);
44
}
45
}
46
47
// ChannelMediaResource::Listener just observes the channel and
48
// forwards notifications to the ChannelMediaResource. We use multiple
49
// listener objects so that when we open a new stream for a seek we can
50
// disconnect the old listener from the ChannelMediaResource and hook up
51
// a new listener, so notifications from the old channel are discarded
52
// and don't confuse us.
53
NS_IMPL_ISUPPORTS(ChannelMediaResource::Listener, nsIRequestObserver,
54
nsIStreamListener, nsIChannelEventSink, nsIInterfaceRequestor,
55
nsIThreadRetargetableStreamListener)
56
57
nsresult ChannelMediaResource::Listener::OnStartRequest(nsIRequest* aRequest) {
58
MOZ_ASSERT(NS_IsMainThread());
59
if (!mResource) return NS_OK;
60
return mResource->OnStartRequest(aRequest, mOffset);
61
}
62
63
nsresult ChannelMediaResource::Listener::OnStopRequest(nsIRequest* aRequest,
64
nsresult aStatus) {
65
MOZ_ASSERT(NS_IsMainThread());
66
if (!mResource) return NS_OK;
67
return mResource->OnStopRequest(aRequest, aStatus);
68
}
69
70
nsresult ChannelMediaResource::Listener::OnDataAvailable(
71
nsIRequest* aRequest, nsIInputStream* aStream, uint64_t aOffset,
72
uint32_t aCount) {
73
// This might happen off the main thread.
74
RefPtr<ChannelMediaResource> res;
75
{
76
MutexAutoLock lock(mMutex);
77
res = mResource;
78
}
79
// Note Rekove() might happen at the same time to reset mResource. We check
80
// the load ID to determine if the data is from an old channel.
81
return res ? res->OnDataAvailable(mLoadID, aStream, aCount) : NS_OK;
82
}
83
84
nsresult ChannelMediaResource::Listener::AsyncOnChannelRedirect(
85
nsIChannel* aOld, nsIChannel* aNew, uint32_t aFlags,
86
nsIAsyncVerifyRedirectCallback* cb) {
87
MOZ_ASSERT(NS_IsMainThread());
88
89
nsresult rv = NS_OK;
90
if (mResource) {
91
rv = mResource->OnChannelRedirect(aOld, aNew, aFlags, mOffset);
92
}
93
94
if (NS_FAILED(rv)) {
95
return rv;
96
}
97
98
cb->OnRedirectVerifyCallback(NS_OK);
99
return NS_OK;
100
}
101
102
nsresult ChannelMediaResource::Listener::CheckListenerChain() { return NS_OK; }
103
104
nsresult ChannelMediaResource::Listener::GetInterface(const nsIID& aIID,
105
void** aResult) {
106
return QueryInterface(aIID, aResult);
107
}
108
109
void ChannelMediaResource::Listener::Revoke() {
110
MOZ_ASSERT(NS_IsMainThread());
111
MutexAutoLock lock(mMutex);
112
mResource = nullptr;
113
}
114
115
static bool IsPayloadCompressed(nsIHttpChannel* aChannel) {
116
nsAutoCString encoding;
117
Unused << aChannel->GetResponseHeader(NS_LITERAL_CSTRING("Content-Encoding"),
118
encoding);
119
return encoding.Length() > 0;
120
}
121
122
nsresult ChannelMediaResource::OnStartRequest(nsIRequest* aRequest,
123
int64_t aRequestOffset) {
124
NS_ASSERTION(mChannel.get() == aRequest, "Wrong channel!");
125
MOZ_DIAGNOSTIC_ASSERT(!mClosed);
126
127
MediaDecoderOwner* owner = mCallback->GetMediaOwner();
128
MOZ_DIAGNOSTIC_ASSERT(owner);
129
dom::HTMLMediaElement* element = owner->GetMediaElement();
130
MOZ_DIAGNOSTIC_ASSERT(element);
131
132
nsresult status;
133
nsresult rv = aRequest->GetStatus(&status);
134
NS_ENSURE_SUCCESS(rv, rv);
135
136
if (status == NS_BINDING_ABORTED) {
137
// Request was aborted before we had a chance to receive any data, or
138
// even an OnStartRequest(). Close the channel. This is important, as
139
// we don't want to mess up our state, as if we're cloned that would
140
// cause the clone to copy incorrect metadata (like whether we're
141
// infinite for example).
142
CloseChannel();
143
return status;
144
}
145
146
if (element->ShouldCheckAllowOrigin()) {
147
// If the request was cancelled by nsCORSListenerProxy due to failing
148
// the CORS security check, send an error through to the media element.
149
if (status == NS_ERROR_DOM_BAD_URI) {
150
mCallback->NotifyNetworkError(MediaResult(status, "CORS not allowed"));
151
return NS_ERROR_DOM_BAD_URI;
152
}
153
}
154
155
nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(aRequest);
156
bool seekable = false;
157
int64_t length = -1;
158
int64_t startOffset = aRequestOffset;
159
160
if (hc) {
161
uint32_t responseStatus = 0;
162
Unused << hc->GetResponseStatus(&responseStatus);
163
bool succeeded = false;
164
Unused << hc->GetRequestSucceeded(&succeeded);
165
166
if (!succeeded && NS_SUCCEEDED(status)) {
167
// HTTP-level error (e.g. 4xx); treat this as a fatal network-level error.
168
// We might get this on a seek.
169
// (Note that lower-level errors indicated by NS_FAILED(status) are
170
// handled in OnStopRequest.)
171
// A 416 error should treated as EOF here... it's possible
172
// that we don't get Content-Length, we read N bytes, then we
173
// suspend and resume, the resume reopens the channel and we seek to
174
// offset N, but there are no more bytes, so we get a 416
175
// "Requested Range Not Satisfiable".
176
if (responseStatus == HTTP_REQUESTED_RANGE_NOT_SATISFIABLE_CODE) {
177
// OnStopRequest will not be fired, so we need to do some of its
178
// work here. Note we need to pass the load ID first so the following
179
// NotifyDataEnded() can pass the ID check.
180
mCacheStream.NotifyLoadID(mLoadID);
181
mCacheStream.NotifyDataEnded(mLoadID, status);
182
} else {
183
mCallback->NotifyNetworkError(
184
MediaResult(NS_ERROR_FAILURE, "HTTP error"));
185
}
186
187
// This disconnects our listener so we don't get any more data. We
188
// certainly don't want an error page to end up in our cache!
189
CloseChannel();
190
return NS_OK;
191
}
192
193
nsAutoCString ranges;
194
Unused << hc->GetResponseHeader(NS_LITERAL_CSTRING("Accept-Ranges"),
195
ranges);
196
bool acceptsRanges =
197
net::nsHttp::FindToken(ranges.get(), "bytes", HTTP_HEADER_VALUE_SEPS);
198
199
int64_t contentLength = -1;
200
const bool isCompressed = IsPayloadCompressed(hc);
201
if (!isCompressed) {
202
hc->GetContentLength(&contentLength);
203
}
204
205
// Check response code for byte-range requests (seeking, chunk requests).
206
// We don't expect to get a 206 response for a compressed stream, but
207
// double check just to be sure.
208
if (!isCompressed && responseStatus == HTTP_PARTIAL_RESPONSE_CODE) {
209
// Parse Content-Range header.
210
int64_t rangeStart = 0;
211
int64_t rangeEnd = 0;
212
int64_t rangeTotal = 0;
213
rv = ParseContentRangeHeader(hc, rangeStart, rangeEnd, rangeTotal);
214
215
// We received 'Content-Range', so the server accepts range requests.
216
bool gotRangeHeader = NS_SUCCEEDED(rv);
217
218
if (gotRangeHeader) {
219
startOffset = rangeStart;
220
// We received 'Content-Range', so the server accepts range requests.
221
// Notify media cache about the length and start offset of data
222
// received. Note: If aRangeTotal == -1, then the total bytes is unknown
223
// at this stage.
224
// For now, tell the decoder that the stream is infinite.
225
if (rangeTotal != -1) {
226
contentLength = std::max(contentLength, rangeTotal);
227
}
228
}
229
acceptsRanges = gotRangeHeader;
230
} else if (responseStatus == HTTP_OK_CODE) {
231
// HTTP_OK_CODE means data will be sent from the start of the stream.
232
startOffset = 0;
233
234
if (aRequestOffset > 0) {
235
// If HTTP_OK_CODE is responded for a non-zero range request, we have
236
// to assume seeking doesn't work.
237
acceptsRanges = false;
238
}
239
}
240
if (aRequestOffset == 0 && contentLength >= 0 &&
241
(responseStatus == HTTP_OK_CODE ||
242
responseStatus == HTTP_PARTIAL_RESPONSE_CODE)) {
243
length = contentLength;
244
}
245
// XXX we probably should examine the Content-Range header in case
246
// the server gave us a range which is not quite what we asked for
247
248
// If we get an HTTP_OK_CODE response to our byte range request,
249
// and the server isn't sending Accept-Ranges:bytes then we don't
250
// support seeking. We also can't seek in compressed streams.
251
seekable = !isCompressed && acceptsRanges;
252
} else {
253
// Not an HTTP channel. Assume data will be sent from position zero.
254
startOffset = 0;
255
}
256
257
// Update principals before OnDataAvailable() putting the data in the cache.
258
// This is important, we want to make sure all principals are updated before
259
// any consumer can see the new data.
260
UpdatePrincipal();
261
if (owner->HasError()) {
262
// Updating the principal resulted in an error. Abort the load.
263
CloseChannel();
264
return NS_OK;
265
}
266
267
mCacheStream.NotifyDataStarted(mLoadID, startOffset, seekable, length);
268
mIsTransportSeekable = seekable;
269
if (mFirstReadLength < 0) {
270
mFirstReadLength = length;
271
}
272
273
mSuspendAgent.Delegate(mChannel);
274
275
// Fires an initial progress event.
276
owner->DownloadProgressed();
277
278
nsCOMPtr<nsIThreadRetargetableRequest> retarget;
279
if (Preferences::GetBool("media.omt_data_delivery.enabled", false) &&
280
(retarget = do_QueryInterface(aRequest))) {
281
// Note this will not always succeed. We need to handle the case where
282
// all resources sharing the same cache might run their data callbacks
283
// on different threads.
284
retarget->RetargetDeliveryTo(mCacheStream.OwnerThread());
285
}
286
287
return NS_OK;
288
}
289
290
bool ChannelMediaResource::IsTransportSeekable() {
291
MOZ_ASSERT(NS_IsMainThread());
292
// We Report the transport as seekable if we know we will never seek into
293
// the underlying transport. As the MediaCache reads content by block of
294
// BLOCK_SIZE bytes, so the content length is less it will always be fully
295
// read from offset = 0 and we can then always successfully seek within this
296
// buffered content.
297
return mIsTransportSeekable ||
298
(mFirstReadLength > 0 &&
299
mFirstReadLength < MediaCacheStream::BLOCK_SIZE);
300
}
301
302
nsresult ChannelMediaResource::ParseContentRangeHeader(
303
nsIHttpChannel* aHttpChan, int64_t& aRangeStart, int64_t& aRangeEnd,
304
int64_t& aRangeTotal) const {
305
NS_ENSURE_ARG(aHttpChan);
306
307
nsAutoCString rangeStr;
308
nsresult rv = aHttpChan->GetResponseHeader(
309
NS_LITERAL_CSTRING("Content-Range"), rangeStr);
310
NS_ENSURE_SUCCESS(rv, rv);
311
NS_ENSURE_FALSE(rangeStr.IsEmpty(), NS_ERROR_ILLEGAL_VALUE);
312
313
// Parse the range header: e.g. Content-Range: bytes 7000-7999/8000.
314
int32_t spacePos = rangeStr.Find(NS_LITERAL_CSTRING(" "));
315
int32_t dashPos = rangeStr.Find(NS_LITERAL_CSTRING("-"), true, spacePos);
316
int32_t slashPos = rangeStr.Find(NS_LITERAL_CSTRING("/"), true, dashPos);
317
318
nsAutoCString aRangeStartText;
319
rangeStr.Mid(aRangeStartText, spacePos + 1, dashPos - (spacePos + 1));
320
aRangeStart = aRangeStartText.ToInteger64(&rv);
321
NS_ENSURE_SUCCESS(rv, rv);
322
NS_ENSURE_TRUE(0 <= aRangeStart, NS_ERROR_ILLEGAL_VALUE);
323
324
nsAutoCString aRangeEndText;
325
rangeStr.Mid(aRangeEndText, dashPos + 1, slashPos - (dashPos + 1));
326
aRangeEnd = aRangeEndText.ToInteger64(&rv);
327
NS_ENSURE_SUCCESS(rv, rv);
328
NS_ENSURE_TRUE(aRangeStart < aRangeEnd, NS_ERROR_ILLEGAL_VALUE);
329
330
nsAutoCString aRangeTotalText;
331
rangeStr.Right(aRangeTotalText, rangeStr.Length() - (slashPos + 1));
332
if (aRangeTotalText[0] == '*') {
333
aRangeTotal = -1;
334
} else {
335
aRangeTotal = aRangeTotalText.ToInteger64(&rv);
336
NS_ENSURE_TRUE(aRangeEnd < aRangeTotal, NS_ERROR_ILLEGAL_VALUE);
337
NS_ENSURE_SUCCESS(rv, rv);
338
}
339
340
LOG("Received bytes [%" PRId64 "] to [%" PRId64 "] of [%" PRId64
341
"] for decoder[%p]",
342
aRangeStart, aRangeEnd, aRangeTotal, mCallback.get());
343
344
return NS_OK;
345
}
346
347
nsresult ChannelMediaResource::OnStopRequest(nsIRequest* aRequest,
348
nsresult aStatus) {
349
NS_ASSERTION(mChannel.get() == aRequest, "Wrong channel!");
350
NS_ASSERTION(!mSuspendAgent.IsSuspended(),
351
"How can OnStopRequest fire while we're suspended?");
352
MOZ_DIAGNOSTIC_ASSERT(!mClosed);
353
354
// Move this request back into the foreground. This is necessary for
355
// requests owned by video documents to ensure the load group fires
356
// OnStopRequest when restoring from session history.
357
nsLoadFlags loadFlags;
358
DebugOnly<nsresult> rv = mChannel->GetLoadFlags(&loadFlags);
359
NS_ASSERTION(NS_SUCCEEDED(rv), "GetLoadFlags() failed!");
360
361
if (loadFlags & nsIRequest::LOAD_BACKGROUND) {
362
ModifyLoadFlags(loadFlags & ~nsIRequest::LOAD_BACKGROUND);
363
}
364
365
// Note that aStatus might have succeeded --- this might be a normal close
366
// --- even in situations where the server cut us off because we were
367
// suspended. It is also possible that the server sends us fewer bytes than
368
// requested. So we need to "reopen on error" in that case too. The only
369
// cases where we don't need to reopen are when *we* closed the stream.
370
// But don't reopen if we need to seek and we don't think we can... that would
371
// cause us to just re-read the stream, which would be really bad.
372
/*
373
* | length | offset | reopen |
374
* +--------+-----------+----------+
375
* | -1 | 0 | yes |
376
* +--------+-----------+----------+
377
* | -1 | > 0 | seekable |
378
* +--------+-----------+----------+
379
* | 0 | X | no |
380
* +--------+-----------+----------+
381
* | > 0 | 0 | yes |
382
* +--------+-----------+----------+
383
* | > 0 | != length | seekable |
384
* +--------+-----------+----------+
385
* | > 0 | == length | no |
386
*/
387
if (aStatus != NS_ERROR_PARSED_DATA_CACHED && aStatus != NS_BINDING_ABORTED) {
388
auto lengthAndOffset = mCacheStream.GetLengthAndOffset();
389
int64_t length = lengthAndOffset.mLength;
390
int64_t offset = lengthAndOffset.mOffset;
391
if ((offset == 0 || mIsTransportSeekable) && offset != length) {
392
// If the stream did close normally, restart the channel if we're either
393
// at the start of the resource, or if the server is seekable and we're
394
// not at the end of stream. We don't restart the stream if we're at the
395
// end because not all web servers handle this case consistently; see:
397
nsresult rv = Seek(offset, false);
398
if (NS_SUCCEEDED(rv)) {
399
return rv;
400
}
401
// Close the streams that failed due to error. This will cause all
402
// client Read and Seek operations on those streams to fail. Blocked
403
// Reads will also be woken up.
404
Close();
405
}
406
}
407
408
mCacheStream.NotifyDataEnded(mLoadID, aStatus);
409
return NS_OK;
410
}
411
412
nsresult ChannelMediaResource::OnChannelRedirect(nsIChannel* aOld,
413
nsIChannel* aNew,
414
uint32_t aFlags,
415
int64_t aOffset) {
416
// OnChannelRedirect() is followed by OnStartRequest() where we will
417
// call mSuspendAgent.Delegate().
418
mChannel = aNew;
419
return SetupChannelHeaders(aOffset);
420
}
421
422
nsresult ChannelMediaResource::CopySegmentToCache(
423
nsIInputStream* aInStream, void* aClosure, const char* aFromSegment,
424
uint32_t aToOffset, uint32_t aCount, uint32_t* aWriteCount) {
425
*aWriteCount = aCount;
426
Closure* closure = static_cast<Closure*>(aClosure);
427
MediaCacheStream* cacheStream = &closure->mResource->mCacheStream;
428
if (cacheStream->OwnerThread()->IsOnCurrentThread()) {
429
cacheStream->NotifyDataReceived(
430
closure->mLoadID, aCount,
431
reinterpret_cast<const uint8_t*>(aFromSegment));
432
return NS_OK;
433
}
434
435
RefPtr<ChannelMediaResource> self = closure->mResource;
436
uint32_t loadID = closure->mLoadID;
437
UniquePtr<uint8_t[]> data = MakeUnique<uint8_t[]>(aCount);
438
memcpy(data.get(), aFromSegment, aCount);
439
cacheStream->OwnerThread()->Dispatch(NS_NewRunnableFunction(
440
"MediaCacheStream::NotifyDataReceived",
441
[self, loadID, data = std::move(data), aCount]() {
442
self->mCacheStream.NotifyDataReceived(loadID, aCount, data.get());
443
}));
444
445
return NS_OK;
446
}
447
448
nsresult ChannelMediaResource::OnDataAvailable(uint32_t aLoadID,
449
nsIInputStream* aStream,
450
uint32_t aCount) {
451
// This might happen off the main thread.
452
Closure closure{aLoadID, this};
453
uint32_t count = aCount;
454
while (count > 0) {
455
uint32_t read;
456
nsresult rv =
457
aStream->ReadSegments(CopySegmentToCache, &closure, count, &read);
458
if (NS_FAILED(rv)) return rv;
459
NS_ASSERTION(read > 0, "Read 0 bytes while data was available?");
460
count -= read;
461
}
462
463
return NS_OK;
464
}
465
466
int64_t ChannelMediaResource::CalculateStreamLength() const {
467
if (!mChannel) {
468
return -1;
469
}
470
471
nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(mChannel);
472
if (!hc) {
473
return -1;
474
}
475
476
bool succeeded = false;
477
Unused << hc->GetRequestSucceeded(&succeeded);
478
if (!succeeded) {
479
return -1;
480
}
481
482
// We can't determine the length of uncompressed payload.
483
const bool isCompressed = IsPayloadCompressed(hc);
484
if (isCompressed) {
485
return -1;
486
}
487
488
int64_t contentLength = -1;
489
if (NS_FAILED(hc->GetContentLength(&contentLength))) {
490
return -1;
491
}
492
493
uint32_t responseStatus = 0;
494
Unused << hc->GetResponseStatus(&responseStatus);
495
if (responseStatus != HTTP_PARTIAL_RESPONSE_CODE) {
496
return contentLength;
497
}
498
499
// We have an HTTP Byte Range response. The Content-Length is the length
500
// of the response, not the resource. We need to parse the Content-Range
501
// header and extract the range total in order to get the stream length.
502
int64_t rangeStart = 0;
503
int64_t rangeEnd = 0;
504
int64_t rangeTotal = 0;
505
bool gotRangeHeader = NS_SUCCEEDED(
506
ParseContentRangeHeader(hc, rangeStart, rangeEnd, rangeTotal));
507
if (gotRangeHeader && rangeTotal != -1) {
508
contentLength = std::max(contentLength, rangeTotal);
509
}
510
return contentLength;
511
}
512
513
nsresult ChannelMediaResource::Open(nsIStreamListener** aStreamListener) {
514
NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
515
MOZ_ASSERT(aStreamListener);
516
MOZ_ASSERT(mChannel);
517
518
int64_t streamLength =
519
mKnownStreamLength < 0 ? CalculateStreamLength() : mKnownStreamLength;
520
nsresult rv = mCacheStream.Init(streamLength);
521
if (NS_FAILED(rv)) {
522
return rv;
523
}
524
525
mSharedInfo = new SharedInfo;
526
mSharedInfo->mResources.AppendElement(this);
527
528
mIsLiveStream = streamLength < 0;
529
mListener = new Listener(this, 0, ++mLoadID);
530
*aStreamListener = mListener;
531
NS_ADDREF(*aStreamListener);
532
return NS_OK;
533
}
534
535
nsresult ChannelMediaResource::OpenChannel(int64_t aOffset) {
536
MOZ_ASSERT(NS_IsMainThread());
537
MOZ_DIAGNOSTIC_ASSERT(!mClosed);
538
MOZ_ASSERT(mChannel);
539
MOZ_ASSERT(!mListener, "Listener should have been removed by now");
540
541
mListener = new Listener(this, aOffset, ++mLoadID);
542
nsresult rv = mChannel->SetNotificationCallbacks(mListener.get());
543
NS_ENSURE_SUCCESS(rv, rv);
544
545
rv = SetupChannelHeaders(aOffset);
546
NS_ENSURE_SUCCESS(rv, rv);
547
548
rv = mChannel->AsyncOpen(mListener);
549
NS_ENSURE_SUCCESS(rv, rv);
550
551
// Tell the media element that we are fetching data from a channel.
552
MediaDecoderOwner* owner = mCallback->GetMediaOwner();
553
MOZ_DIAGNOSTIC_ASSERT(owner);
554
dom::HTMLMediaElement* element = owner->GetMediaElement();
555
MOZ_DIAGNOSTIC_ASSERT(element);
556
element->DownloadResumed();
557
558
return NS_OK;
559
}
560
561
nsresult ChannelMediaResource::SetupChannelHeaders(int64_t aOffset) {
562
MOZ_ASSERT(NS_IsMainThread());
563
MOZ_DIAGNOSTIC_ASSERT(!mClosed);
564
565
// Always use a byte range request even if we're reading from the start
566
// of the resource.
567
// This enables us to detect if the stream supports byte range
568
// requests, and therefore seeking, early.
569
nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(mChannel);
570
if (hc) {
571
// Use |mOffset| if seeking in a complete file download.
572
nsAutoCString rangeString("bytes=");
573
rangeString.AppendInt(aOffset);
574
rangeString.Append('-');
575
nsresult rv =
576
hc->SetRequestHeader(NS_LITERAL_CSTRING("Range"), rangeString, false);
577
NS_ENSURE_SUCCESS(rv, rv);
578
579
// Send Accept header for video and audio types only (Bug 489071)
580
MediaDecoderOwner* owner = mCallback->GetMediaOwner();
581
MOZ_DIAGNOSTIC_ASSERT(owner);
582
dom::HTMLMediaElement* element = owner->GetMediaElement();
583
MOZ_DIAGNOSTIC_ASSERT(element);
584
element->SetRequestHeaders(hc);
585
} else {
586
NS_ASSERTION(aOffset == 0, "Don't know how to seek on this channel type");
587
return NS_ERROR_FAILURE;
588
}
589
return NS_OK;
590
}
591
592
RefPtr<GenericPromise> ChannelMediaResource::Close() {
593
NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
594
595
if (!mClosed) {
596
CloseChannel();
597
mClosed = true;
598
return mCacheStream.Close();
599
}
600
return GenericPromise::CreateAndResolve(true, __func__);
601
}
602
603
already_AddRefed<nsIPrincipal> ChannelMediaResource::GetCurrentPrincipal() {
604
MOZ_ASSERT(NS_IsMainThread());
605
return do_AddRef(mSharedInfo->mPrincipal);
606
}
607
608
bool ChannelMediaResource::HadCrossOriginRedirects() {
609
MOZ_ASSERT(NS_IsMainThread());
610
return mSharedInfo->mHadCrossOriginRedirects;
611
}
612
613
bool ChannelMediaResource::CanClone() {
614
return !mClosed && mCacheStream.IsAvailableForSharing();
615
}
616
617
already_AddRefed<BaseMediaResource> ChannelMediaResource::CloneData(
618
MediaResourceCallback* aCallback) {
619
MOZ_ASSERT(NS_IsMainThread());
620
MOZ_ASSERT(CanClone(), "Stream can't be cloned");
621
622
RefPtr<ChannelMediaResource> resource =
623
new ChannelMediaResource(aCallback, nullptr, mURI, mKnownStreamLength);
624
625
resource->mIsLiveStream = mIsLiveStream;
626
resource->mIsTransportSeekable = mIsTransportSeekable;
627
resource->mSharedInfo = mSharedInfo;
628
mSharedInfo->mResources.AppendElement(resource.get());
629
630
// Initially the clone is treated as suspended by the cache, because
631
// we don't have a channel. If the cache needs to read data from the clone
632
// it will call CacheClientResume (or CacheClientSeek with aResume true)
633
// which will recreate the channel. This way, if all of the media data
634
// is already in the cache we don't create an unnecessary HTTP channel
635
// and perform a useless HTTP transaction.
636
resource->mCacheStream.InitAsClone(&mCacheStream);
637
return resource.forget();
638
}
639
640
void ChannelMediaResource::CloseChannel() {
641
NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
642
643
if (mChannel) {
644
mSuspendAgent.Revoke();
645
// The status we use here won't be passed to the decoder, since
646
// we've already revoked the listener. It can however be passed
647
// to nsDocumentViewer::LoadComplete if our channel is the one
648
// that kicked off creation of a video document. We don't want that
649
// document load to think there was an error.
650
// NS_ERROR_PARSED_DATA_CACHED is the best thing we have for that
651
// at the moment.
652
mChannel->Cancel(NS_ERROR_PARSED_DATA_CACHED);
653
mChannel = nullptr;
654
}
655
656
if (mListener) {
657
mListener->Revoke();
658
mListener = nullptr;
659
}
660
}
661
662
nsresult ChannelMediaResource::ReadFromCache(char* aBuffer, int64_t aOffset,
663
uint32_t aCount) {
664
return mCacheStream.ReadFromCache(aBuffer, aOffset, aCount);
665
}
666
667
nsresult ChannelMediaResource::ReadAt(int64_t aOffset, char* aBuffer,
668
uint32_t aCount, uint32_t* aBytes) {
669
NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
670
return mCacheStream.ReadAt(aOffset, aBuffer, aCount, aBytes);
671
}
672
673
void ChannelMediaResource::ThrottleReadahead(bool bThrottle) {
674
mCacheStream.ThrottleReadahead(bThrottle);
675
}
676
677
nsresult ChannelMediaResource::GetCachedRanges(MediaByteRangeSet& aRanges) {
678
return mCacheStream.GetCachedRanges(aRanges);
679
}
680
681
void ChannelMediaResource::Suspend(bool aCloseImmediately) {
682
NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread");
683
684
if (mClosed) {
685
// Nothing to do when we are closed.
686
return;
687
}
688
689
MediaDecoderOwner* owner = mCallback->GetMediaOwner();
690
MOZ_DIAGNOSTIC_ASSERT(owner);
691
dom::HTMLMediaElement* element = owner->GetMediaElement();
692
MOZ_DIAGNOSTIC_ASSERT(element);
693
694
if (mChannel && aCloseImmediately && mIsTransportSeekable) {
695
CloseChannel();
696
}
697
698
if (mSuspendAgent.Suspend()) {
699
element->DownloadSuspended();
700
}
701
}
702
703
void ChannelMediaResource::Resume() {
704
NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread");
705
706
if (mClosed) {
707
// Nothing to do when we are closed.
708
return;
709
}
710
711
MediaDecoderOwner* owner = mCallback->GetMediaOwner();
712
MOZ_DIAGNOSTIC_ASSERT(owner);
713
dom::HTMLMediaElement* element = owner->GetMediaElement();
714
MOZ_DIAGNOSTIC_ASSERT(element);
715
716
if (mSuspendAgent.Resume()) {
717
if (mChannel) {
718
// Just wake up our existing channel
719
element->DownloadResumed();
720
} else {
721
mCacheStream.NotifyResume();
722
}
723
}
724
}
725
726
nsresult ChannelMediaResource::RecreateChannel() {
727
MOZ_DIAGNOSTIC_ASSERT(!mClosed);
728
729
nsLoadFlags loadFlags = nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE_IF_BUSY |
730
(mLoadInBackground ? nsIRequest::LOAD_BACKGROUND : 0);
731
732
MediaDecoderOwner* owner = mCallback->GetMediaOwner();
733
MOZ_DIAGNOSTIC_ASSERT(owner);
734
dom::HTMLMediaElement* element = owner->GetMediaElement();
735
MOZ_DIAGNOSTIC_ASSERT(element);
736
737
nsCOMPtr<nsILoadGroup> loadGroup = element->GetDocumentLoadGroup();
738
NS_ENSURE_TRUE(loadGroup, NS_ERROR_NULL_POINTER);
739
740
nsSecurityFlags securityFlags =
741
element->ShouldCheckAllowOrigin()
742
? nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS
743
: nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS;
744
745
if (element->GetCORSMode() == CORS_USE_CREDENTIALS) {
746
securityFlags |= nsILoadInfo::SEC_COOKIES_INCLUDE;
747
}
748
749
MOZ_ASSERT(element->IsAnyOfHTMLElements(nsGkAtoms::audio, nsGkAtoms::video));
750
nsContentPolicyType contentPolicyType =
751
element->IsHTMLElement(nsGkAtoms::audio)
752
? nsIContentPolicy::TYPE_INTERNAL_AUDIO
753
: nsIContentPolicy::TYPE_INTERNAL_VIDEO;
754
755
// If element has 'triggeringprincipal' attribute, we will use the value as
756
// triggeringPrincipal for the channel, otherwise it will default to use
757
// aElement->NodePrincipal().
758
// This function returns true when element has 'triggeringprincipal', so if
759
// setAttrs is true we will override the origin attributes on the channel
760
// later.
761
nsCOMPtr<nsIPrincipal> triggeringPrincipal;
762
bool setAttrs = nsContentUtils::QueryTriggeringPrincipal(
763
element, getter_AddRefs(triggeringPrincipal));
764
765
nsresult rv = NS_NewChannelWithTriggeringPrincipal(
766
getter_AddRefs(mChannel), mURI, element, triggeringPrincipal,
767
securityFlags, contentPolicyType,
768
nullptr, // aPerformanceStorage
769
loadGroup,
770
nullptr, // aCallbacks
771
loadFlags);
772
NS_ENSURE_SUCCESS(rv, rv);
773
774
if (setAttrs) {
775
nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
776
// The function simply returns NS_OK, so we ignore the return value.
777
Unused << loadInfo->SetOriginAttributes(
778
triggeringPrincipal->OriginAttributesRef());
779
}
780
781
nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(mChannel));
782
if (cos) {
783
// Unconditionally disable throttling since we want the media to fluently
784
// play even when we switch the tab to background.
785
cos->AddClassFlags(nsIClassOfService::DontThrottle);
786
}
787
788
return rv;
789
}
790
791
void ChannelMediaResource::CacheClientNotifyDataReceived() {
792
mCallback->AbstractMainThread()->Dispatch(NewRunnableMethod(
793
"MediaResourceCallback::NotifyDataArrived", mCallback.get(),
794
&MediaResourceCallback::NotifyDataArrived));
795
}
796
797
void ChannelMediaResource::CacheClientNotifyDataEnded(nsresult aStatus) {
798
mCallback->AbstractMainThread()->Dispatch(NS_NewRunnableFunction(
799
"ChannelMediaResource::CacheClientNotifyDataEnded",
800
[self = RefPtr<ChannelMediaResource>(this), aStatus]() {
801
if (NS_SUCCEEDED(aStatus)) {
802
self->mIsLiveStream = false;
803
}
804
self->mCallback->NotifyDataEnded(aStatus);
805
}));
806
}
807
808
void ChannelMediaResource::CacheClientNotifyPrincipalChanged() {
809
NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread");
810
811
mCallback->NotifyPrincipalChanged();
812
}
813
814
void ChannelMediaResource::UpdatePrincipal() {
815
MOZ_ASSERT(NS_IsMainThread());
816
MOZ_ASSERT(mChannel);
817
nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
818
if (!secMan) {
819
return;
820
}
821
nsCOMPtr<nsIPrincipal> principal;
822
secMan->GetChannelResultPrincipal(mChannel, getter_AddRefs(principal));
823
if (nsContentUtils::CombineResourcePrincipals(&mSharedInfo->mPrincipal,
824
principal)) {
825
for (auto* r : mSharedInfo->mResources) {
826
r->CacheClientNotifyPrincipalChanged();
827
}
828
}
829
830
// ChannelMediaResource can recreate the channel. When this happens, we don't
831
// want to overwrite mHadCrossOriginRedirects because the new channel could
832
// skip intermediate redirects.
833
if (!mSharedInfo->mHadCrossOriginRedirects) {
834
nsCOMPtr<nsITimedChannel> timedChannel = do_QueryInterface(mChannel);
835
if (timedChannel) {
836
bool allRedirectsSameOrigin = false;
837
mSharedInfo->mHadCrossOriginRedirects =
838
NS_SUCCEEDED(timedChannel->GetAllRedirectsSameOrigin(
839
&allRedirectsSameOrigin)) &&
840
!allRedirectsSameOrigin;
841
}
842
}
843
}
844
845
void ChannelMediaResource::CacheClientNotifySuspendedStatusChanged(
846
bool aSuspended) {
847
mCallback->AbstractMainThread()->Dispatch(NewRunnableMethod<bool>(
848
"MediaResourceCallback::NotifySuspendedStatusChanged", mCallback.get(),
849
&MediaResourceCallback::NotifySuspendedStatusChanged, aSuspended));
850
}
851
852
nsresult ChannelMediaResource::Seek(int64_t aOffset, bool aResume) {
853
MOZ_ASSERT(NS_IsMainThread());
854
855
if (mClosed) {
856
// Nothing to do when we are closed.
857
return NS_OK;
858
}
859
860
LOG("Seek requested for aOffset [%" PRId64 "]", aOffset);
861
862
CloseChannel();
863
864
if (aResume) {
865
mSuspendAgent.Resume();
866
}
867
868
// Don't create a new channel if we are still suspended. The channel will
869
// be recreated when we are resumed.
870
if (mSuspendAgent.IsSuspended()) {
871
return NS_OK;
872
}
873
874
nsresult rv = RecreateChannel();
875
NS_ENSURE_SUCCESS(rv, rv);
876
877
return OpenChannel(aOffset);
878
}
879
880
void ChannelMediaResource::CacheClientSeek(int64_t aOffset, bool aResume) {
881
RefPtr<ChannelMediaResource> self = this;
882
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
883
"ChannelMediaResource::Seek", [self, aOffset, aResume]() {
884
nsresult rv = self->Seek(aOffset, aResume);
885
if (NS_FAILED(rv)) {
886
// Close the streams that failed due to error. This will cause all
887
// client Read and Seek operations on those streams to fail. Blocked
888
// Reads will also be woken up.
889
self->Close();
890
}
891
});
892
mCallback->AbstractMainThread()->Dispatch(r.forget());
893
}
894
895
void ChannelMediaResource::CacheClientSuspend() {
896
mCallback->AbstractMainThread()->Dispatch(
897
NewRunnableMethod<bool>("ChannelMediaResource::Suspend", this,
898
&ChannelMediaResource::Suspend, false));
899
}
900
901
void ChannelMediaResource::CacheClientResume() {
902
mCallback->AbstractMainThread()->Dispatch(NewRunnableMethod(
903
"ChannelMediaResource::Resume", this, &ChannelMediaResource::Resume));
904
}
905
906
int64_t ChannelMediaResource::GetNextCachedData(int64_t aOffset) {
907
return mCacheStream.GetNextCachedData(aOffset);
908
}
909
910
int64_t ChannelMediaResource::GetCachedDataEnd(int64_t aOffset) {
911
return mCacheStream.GetCachedDataEnd(aOffset);
912
}
913
914
bool ChannelMediaResource::IsDataCachedToEndOfResource(int64_t aOffset) {
915
return mCacheStream.IsDataCachedToEndOfStream(aOffset);
916
}
917
918
bool ChannelMediaResource::IsSuspended() { return mSuspendAgent.IsSuspended(); }
919
920
void ChannelMediaResource::SetReadMode(MediaCacheStream::ReadMode aMode) {
921
mCacheStream.SetReadMode(aMode);
922
}
923
924
void ChannelMediaResource::SetPlaybackRate(uint32_t aBytesPerSecond) {
925
mCacheStream.SetPlaybackRate(aBytesPerSecond);
926
}
927
928
void ChannelMediaResource::Pin() { mCacheStream.Pin(); }
929
930
void ChannelMediaResource::Unpin() { mCacheStream.Unpin(); }
931
932
double ChannelMediaResource::GetDownloadRate(bool* aIsReliable) {
933
return mCacheStream.GetDownloadRate(aIsReliable);
934
}
935
936
int64_t ChannelMediaResource::GetLength() { return mCacheStream.GetLength(); }
937
938
void ChannelMediaResource::GetDebugInfo(dom::MediaResourceDebugInfo& aInfo) {
939
mCacheStream.GetDebugInfo(aInfo.mCacheStream);
940
}
941
942
// ChannelSuspendAgent
943
944
bool ChannelSuspendAgent::Suspend() {
945
MOZ_ASSERT(NS_IsMainThread());
946
SuspendInternal();
947
if (++mSuspendCount == 1) {
948
mCacheStream.NotifyClientSuspended(true);
949
return true;
950
}
951
return false;
952
}
953
954
void ChannelSuspendAgent::SuspendInternal() {
955
MOZ_ASSERT(NS_IsMainThread());
956
if (mChannel) {
957
bool isPending = false;
958
nsresult rv = mChannel->IsPending(&isPending);
959
if (NS_SUCCEEDED(rv) && isPending && !mIsChannelSuspended) {
960
mChannel->Suspend();
961
mIsChannelSuspended = true;
962
}
963
}
964
}
965
966
bool ChannelSuspendAgent::Resume() {
967
MOZ_ASSERT(NS_IsMainThread());
968
MOZ_ASSERT(IsSuspended(), "Resume without suspend!");
969
970
if (--mSuspendCount == 0) {
971
if (mChannel && mIsChannelSuspended) {
972
mChannel->Resume();
973
mIsChannelSuspended = false;
974
}
975
mCacheStream.NotifyClientSuspended(false);
976
return true;
977
}
978
return false;
979
}
980
981
void ChannelSuspendAgent::Delegate(nsIChannel* aChannel) {
982
MOZ_ASSERT(NS_IsMainThread());
983
MOZ_ASSERT(aChannel);
984
MOZ_ASSERT(!mChannel, "The previous channel not closed.");
985
MOZ_ASSERT(!mIsChannelSuspended);
986
987
mChannel = aChannel;
988
// Ensure the suspend status of the channel matches our suspend count.
989
if (IsSuspended()) {
990
SuspendInternal();
991
}
992
}
993
994
void ChannelSuspendAgent::Revoke() {
995
MOZ_ASSERT(NS_IsMainThread());
996
997
if (!mChannel) {
998
// Channel already revoked. Nothing to do.
999
return;
1000
}
1001
1002
// Before closing the channel, it needs to be resumed to make sure its
1003
// internal state is correct. Besides, We need to suspend the channel after
1004
// recreating.
1005
if (mIsChannelSuspended) {
1006
mChannel->Resume();
1007
mIsChannelSuspended = false;
1008
}
1009
mChannel = nullptr;
1010
}
1011
1012
bool ChannelSuspendAgent::IsSuspended() {
1013
MOZ_ASSERT(NS_IsMainThread());
1014
return (mSuspendCount > 0);
1015
}
1016
1017
} // namespace mozilla