Bug 1370177 - Treat compressed HTTP channels as having infinite length and as unseekable in media code. r=jwwang. draft
authorChris Pearce <cpearce@mozilla.com>
Tue, 04 Jul 2017 15:31:48 +1200
changeset 603935 ca561776a684a4a0a39a830ecda499732666daaa
parent 603934 1ae21d0f202e24f6e0236d3f2d2363ab930b0c2e
child 636040 d0dcacf9bc3633ae69e56eef226ff2bcee254617
push id66905
push userbmo:cpearce@mozilla.com
push dateWed, 05 Jul 2017 01:34:34 +0000
reviewersjwwang
bugs1370177
milestone56.0a1
Bug 1370177 - Treat compressed HTTP channels as having infinite length and as unseekable in media code. r=jwwang. The problem here is that the server is setting the Content-Length to the size of the gzipped file, which is confusing our media code as that's not the length of the decompressed stream. Necko's decompressor is streaming, so we can't know the length of the stream in advance unless we wait for the entire file to download first. We also can't seek in compressed HTTP resources anyway. So just have our code assume that compressed HTTP channels are of infinite length and have un unseekable transport. Then we can seek in the buffered regions at least. MozReview-Commit-ID: 9SiLuMZGSeJ
dom/media/MediaResource.cpp
--- a/dom/media/MediaResource.cpp
+++ b/dom/media/MediaResource.cpp
@@ -174,16 +174,24 @@ ChannelMediaResource::Listener::AsyncOnC
 }
 
 nsresult
 ChannelMediaResource::Listener::GetInterface(const nsIID & aIID, void **aResult)
 {
   return QueryInterface(aIID, aResult);
 }
 
+static bool
+IsPayloadCompressed(nsIHttpChannel* aChannel)
+{
+  nsAutoCString encoding;
+  Unused << aChannel->GetResponseHeader(NS_LITERAL_CSTRING("Content-Encoding"), encoding);
+  return encoding.Length() > 0;
+}
+
 nsresult
 ChannelMediaResource::OnStartRequest(nsIRequest* aRequest)
 {
   NS_ASSERTION(mChannel.get() == aRequest, "Wrong channel!");
 
   MediaDecoderOwner* owner = mCallback->GetMediaOwner();
   NS_ENSURE_TRUE(owner, NS_ERROR_FAILURE);
   dom::HTMLMediaElement* element = owner->GetMediaElement();
@@ -246,30 +254,35 @@ ChannelMediaResource::OnStartRequest(nsI
     nsAutoCString ranges;
     Unused << hc->GetResponseHeader(NS_LITERAL_CSTRING("Accept-Ranges"),
                                     ranges);
     bool acceptsRanges = ranges.EqualsLiteral("bytes");
     // True if this channel will not return an unbounded amount of data
     bool dataIsBounded = false;
 
     int64_t contentLength = -1;
-    hc->GetContentLength(&contentLength);
+    const bool isCompressed = IsPayloadCompressed(hc);
+    if (!isCompressed) {
+      hc->GetContentLength(&contentLength);
+    }
     if (contentLength >= 0 &&
         (responseStatus == HTTP_OK_CODE ||
          responseStatus == HTTP_PARTIAL_RESPONSE_CODE)) {
       // "OK" status means Content-Length is for the whole resource.
       // Since that's bounded, we know we have a finite-length resource.
       dataIsBounded = true;
     }
 
     // Assume Range requests have a bounded upper limit unless the
     // Content-Range header tells us otherwise.
     bool boundedSeekLimit = true;
     // Check response code for byte-range requests (seeking, chunk requests).
-    if (responseStatus == HTTP_PARTIAL_RESPONSE_CODE) {
+    // We don't expect to get a 206 response for a compressed stream, but
+    // double check just to be sure.
+    if (!isCompressed && responseStatus == HTTP_PARTIAL_RESPONSE_CODE) {
       // Parse Content-Range header.
       int64_t rangeStart = 0;
       int64_t rangeEnd = 0;
       int64_t rangeTotal = 0;
       rv = ParseContentRangeHeader(hc, rangeStart, rangeEnd, rangeTotal);
 
       // We received 'Content-Range', so the server accepts range requests.
       bool gotRangeHeader = NS_SUCCEEDED(rv);
@@ -308,18 +321,18 @@ ChannelMediaResource::OnStartRequest(nsI
          responseStatus == HTTP_PARTIAL_RESPONSE_CODE)) {
       mCacheStream.NotifyDataLength(contentLength);
     }
     // XXX we probably should examine the Content-Range header in case
     // the server gave us a range which is not quite what we asked for
 
     // If we get an HTTP_OK_CODE response to our byte range request,
     // and the server isn't sending Accept-Ranges:bytes then we don't
-    // support seeking.
-    seekable = acceptsRanges;
+    // support seeking. We also can't seek in compressed streams.
+    seekable = !isCompressed && acceptsRanges;
     if (seekable && boundedSeekLimit) {
       // If range requests are supported, and we did not see an unbounded
       // upper range limit, we assume the resource is bounded.
       dataIsBounded = true;
     }
 
     mCallback->SetInfinite(!dataIsBounded);
   }
@@ -526,17 +539,17 @@ ChannelMediaResource::OnDataAvailable(ns
 
 nsresult ChannelMediaResource::Open(nsIStreamListener **aStreamListener)
 {
   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
 
   int64_t cl = -1;
   if (mChannel) {
     nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(mChannel);
-    if (hc) {
+    if (hc && !IsPayloadCompressed(hc)) {
       if (NS_FAILED(hc->GetContentLength(&cl))) {
         cl = -1;
       }
     }
   }
 
   nsresult rv = mCacheStream.Init(cl);
   if (NS_FAILED(rv))
@@ -564,17 +577,17 @@ nsresult ChannelMediaResource::OpenChann
     *aStreamListener = nullptr;
   }
 
   // Set the content length, if it's available as an HTTP header.
   // This ensures that MediaResource wrapping objects for platform libraries
   // that expect to know the length of a resource can get it before
   // OnStartRequest() fires.
   nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(mChannel);
-  if (hc) {
+  if (hc && !IsPayloadCompressed(hc)) {
     int64_t cl = -1;
     if (NS_SUCCEEDED(hc->GetContentLength(&cl)) && cl != -1) {
       mCacheStream.NotifyDataLength(cl);
     }
   }
 
   mListener = new Listener(this);
   if (aStreamListener) {