8

Problem

As the title says, I'm seeing an issue where the browser (when seeking through a video) is constantly making requests for ranges of bytes it should already have.

enter image description here

Code

I've provided a minimal code sample; to replicate the issue, whip around the current time control on the video and look at your network log.

window.onload = function() {

  var myVideo = document.getElementById('my-video');

  myVideo.addEventListener('progress', function() {
    var bufferedEnd = myVideo.buffered.end(myVideo.buffered.length - 1);
    var duration = myVideo.duration;
    if (duration > 0) {
      document.getElementById('buffered-amount').style.width = ((bufferedEnd / duration) * 100) + "%";
    }
  });

  myVideo.addEventListener('timeupdate', function() {
    var duration = myVideo.duration;
    if (duration > 0) {
      document.getElementById('progress-amount').style.width = ((myVideo.currentTime / duration) * 100) + "%";
    }
  });
}
.buffered {
  height: 20px;
  position: relative;
  background: #555;
  width: 300px;
}

#buffered-amount {
  display: block;
  height: 100%;
  background-color: #777;
  width: 0;
}

.progress {
  margin-top: -20px;
  height: 20px;
  position: relative;
  width: 300px;
}

#progress-amount {
  display: block;
  height: 100%;
  background-color: #595;
  width: 0;
}
<video id="my-video" controls preload="auto"><source src="https://media-dev.ozone.tech/media/045728df-7431-4548-8353-318357cc2643/video_preview-1655322764.5499873.mp4?Expires=1656287999&Signature=3RnkLu03JreuUZk9bICqqC5IwGiq~tWpBCaUSfyGb9W9VLPfw6Na4zh~tDTxxDjIirhocj7es2T0ONm0L6XsuFxsCx2T9-CANok014Kt6ddHEaUmuOi5uztEWyXpLmI3ussq4dc5lW2Xw5WH6uV-5Z8fIlIQehMxQK-XQ6RpdHhWBQLsmfPBqSSPnDk6KH5fkK-xCwQmxUwAN1nRVH8H6p~43~E6MZa7gSS~i1717~FlHto3depk~AvREjm59T1rNBvaZeb6ZI-pnYAf52wwQs3pWXln7PCtiIOYQCxE10l4nkOvJ--KGmPA0jOeSuv0dCRUi3QLZD5JxsJgBVM4sA__&Key-Pair-Id=K16V0JZ9ZAOTC6" type="video/mp4"></video>
<div class="buffered">
  <span id="buffered-amount"></span>
</div>
<div class="progress">
  <span id="progress-amount"></span>
</div>

Question

Can someone explain why this is happening and how I can prevent it? Seems like this could potentially be a bug but since all the browsers are doing it, I wonder if it's something on my end; maybe the way we encode the video?

Derek Pollard
  • 6,953
  • 6
  • 39
  • 59
  • It looks like how HLS and DASH stream in small chunks, do you know which company is your streaming service? – zer00ne Jun 17 '22 at 01:01
  • I'm using cloudfront + S3 for this – Derek Pollard Jun 17 '22 at 01:01
  • Yeah that's probably it, CloudFront is the CDN and that's needed for HLS and DASH. When your seeking, the service is actually feeding you the chunks needed. – zer00ne Jun 17 '22 at 01:02
  • @zer00ne why isn't it keeping the chunks in memory? I'm seeing it go back for bytes it should already have – Derek Pollard Jun 17 '22 at 01:05
  • Sorry, I only used Cloudfront for the Edge networks, I don't know any particulars about that aspect. At least you have a potential lead, hopefully there'll be someone more knowledgeable that can help. – zer00ne Jun 17 '22 at 01:13
  • @zer00ne I appreciate your additional knowledge – Derek Pollard Jun 17 '22 at 01:15
  • Weird I don't have the same behavior at all, here I get a single nice 206 response through HTTP/2 which does handle all the range requests from the same request, even after seeking manually. – Kaiido Jun 17 '22 at 06:27
  • @Kaiido interesting, what browser / OS? – Derek Pollard Jun 17 '22 at 16:00
  • The progress bar under the video is wrong. The light gray part isn't showing what has been buffered. Doing that is actually pretty hard: https://stackoverflow.com/questions/18422517/html5-video-buffered-attribute-features/18624833#18624833 – Swiffy Jun 17 '22 at 23:57
  • You need to make sure that your server supports range request. The server should both accept and obey to the Range header, and respond with the Content-Range & Content-Length header. I cant verify this because your server responds with 403. Take a look at the docs for range request https://developer.mozilla.org/en-US/docs/Web/HTTP/Range_requests – Benjaco Jun 20 '22 at 16:16
  • And Accept-Ranges. this can also be helpful https://web.dev/video-and-source-tags/ – Benjaco Jun 21 '22 at 12:02
  • It does support and obey these requests @Benjaco – Derek Pollard Jun 21 '22 at 17:41
  • @Benjaco updated the video; I apologize for it going out – Derek Pollard Jun 21 '22 at 18:42
  • It's working perfectly fine here, be aware that if you see the first 2 minutes, then skip to 10, and then go back to the start, then it'll fetch the first part again. But if I just start playing, then it will just keep the stream open and get the data it need. Both tried firefox and chrome. – Benjaco Jun 22 '22 at 07:59
  • Try open the requests and see if its fetching the same Content-Range – Benjaco Jun 22 '22 at 08:00
  • @Benjaco - problem is that what we're trying to do means the video wont be playing. We are trying to skip around to various parts of the video and have it not request that range again – Derek Pollard Jun 22 '22 at 17:24
  • @DerekPollard, ahrr, im sorry for the comment then. Then I see 2 solutions: first one could maybe be resetting the src with the #t=[start_time] tag which tells the browser from where it should stream the video, this will refetch the resource if you are skipping to the same parts thoug. – Benjaco Jun 23 '22 at 07:12
  • Else you can use the media source api https://developer.mozilla.org/en-US/docs/Web/API/MediaSource where you fetch the chunks your self, be aware that it will discard the video it already have played, so you need to cache the chunks your self during the lifetime of the page, it can be done using https://developer.mozilla.org/en-US/docs/Web/API/Cache. But this can be a bigger task implementing, might be better to see if you can find a video player which supports this feature – Benjaco Jun 23 '22 at 07:12
  • I believe the buffer progress is sorted now, it behaves as expected regardless of where you skip around to backwards or forward. The ranges definitely does not keep the complete cache for the whole period between start and end, more aggressive caching may be accomplished with a service worker but even then the browser does not have unlimited storage and cache cannot be guaranteed. Cache will always be a best attempt effort. – nickl- Jun 23 '22 at 09:43

1 Answers1

2

When we reference the specification on TimeRanges we find the following definition.

When a TimeRanges object is said to be a normalized TimeRanges object, the ranges it represents must obey the following criteria:

  • The start of a range must be greater than the end of all earlier ranges.
  • The start of a range must be less than or equal to the end of that same range.

In other words, the ranges in such an object are ordered, don't overlap, and don't touch (adjacent ranges are folded into one bigger range). A range can be empty (referencing just a single moment in time), e.g. to indicate that only one frame is currently buffered in the case that the user agent has discarded the entire media resource except for the current frame, when a media element is paused.

Ranges in a TimeRanges object must be inclusive.

Thus, the end of a range would be equal to the start of a following adjacent (touching but not overlapping) range. Similarly, a range covering a whole timeline anchored at zero would have a start equal to zero and an end equal to the duration of the timeline.

Which if I were to interpret I would imagine that only if the end of the smallest TimeRange (being index 0) is equal to duration will 100% buffering be achieved, which is not the case even at the end multiple ranges may remain. This is contradicted by the the Mozilla documentation, so much for a sanity check.

Basically the only thing we need to deduce is the buffered progress, consoling ourselves that no where is there any guarantee or even mention that buffered means already downloaded, stored and readily available frames (never needed to be fetched again). In fact the opposite is true, the possibility exists that "a range can be empty (referencing just a single moment in time)".

Since the only buffered time ranges of any use will always be future content considering current time as our base, which may not necessarily be the smallest range.

Based on deductive reasoning we may safely assume:

  • That total buffered must at least be current time
  • Adding only the difference from future range end, and
  • Include the sum of the difference between start and end of any subsequent ranges

Or in other more eloquent words:

function bufferedTotal(buffered, currentTime) {
    let total = currentTime;
    for (i = 0; i < buffered.length; i++)
        if (buffered.end(i) > currentTime)
            if (buffered.start(i) < currentTime)
                total += buffered.end(i) - currentTime;
            else
                total += buffered.end(i) - buffered.start(i);
    return total;
}

With all our ducks neatly in a row this might just be considered done.

window.onload = function() {

  function bufferedTotal(buffered, currentTime) {
     let total = currentTime;
     for (i = 0; i < buffered.length; i++)
          if (buffered.end(i) > currentTime)
              if (buffered.start(i) < currentTime)
                  total += buffered.end(i) - currentTime;
              else
                  total += buffered.end(i) - buffered.start(i);
      return total;
  }

  var myVideo = document.getElementById('my-video');

  myVideo.addEventListener('progress', function() {
    var bufferedEnd = bufferedTotal(myVideo.buffered, myVideo.currentTime);
    var duration = myVideo.duration;
    if (duration > 0) {
      document.getElementById('buffered-amount').style.width = ((bufferedEnd / duration) * 100) + "%";
    }
  });

  myVideo.addEventListener('timeupdate', function() {
    var duration = myVideo.duration;
    if (duration > 0) {
      document.getElementById('progress-amount').style.width = ((myVideo.currentTime / duration) * 100) + "%";
    }
  });
}
.buffered {
  height: 20px;
  position: relative;
  background: #555;
  width: 300px;
}

#buffered-amount {
  display: block;
  height: 100%;
  background-color: #777;
  width: 0;
}

.progress {
  margin-top: -20px;
  height: 20px;
  position: relative;
  width: 300px;
}

#progress-amount {
  display: block;
  height: 100%;
  background-color: #595;
  width: 0;
}
<video id="my-video" controls preload="auto"><source src="https://media-dev.ozone.tech/media/045728df-7431-4548-8353-318357cc2643/video_preview-1655322764.5499873.mp4?Expires=1656287999&Signature=3RnkLu03JreuUZk9bICqqC5IwGiq~tWpBCaUSfyGb9W9VLPfw6Na4zh~tDTxxDjIirhocj7es2T0ONm0L6XsuFxsCx2T9-CANok014Kt6ddHEaUmuOi5uztEWyXpLmI3ussq4dc5lW2Xw5WH6uV-5Z8fIlIQehMxQK-XQ6RpdHhWBQLsmfPBqSSPnDk6KH5fkK-xCwQmxUwAN1nRVH8H6p~43~E6MZa7gSS~i1717~FlHto3depk~AvREjm59T1rNBvaZeb6ZI-pnYAf52wwQs3pWXln7PCtiIOYQCxE10l4nkOvJ--KGmPA0jOeSuv0dCRUi3QLZD5JxsJgBVM4sA__&Key-Pair-Id=K16V0JZ9ZAOTC6" type="video/mp4"></video>
<div class="buffered">
  <span id="buffered-amount"></span>
</div>
<div class="progress">
  <span id="progress-amount"></span>
</div>
Derek Pollard
  • 6,953
  • 6
  • 39
  • 59
nickl-
  • 8,417
  • 4
  • 42
  • 56
  • Okay, so what im wanting to do is probably technically not feasible at the moment – Derek Pollard Jun 23 '22 at 12:43
  • The contradictory documentation was also throwing me for a loop; maybe I should ask Mozilla team to clarify – Derek Pollard Jun 23 '22 at 12:44
  • Also, wanted to clarify that the loader is just for visual viewers; the real goal is to scrub through the not playing video and have it update real time without fetching bytes it may already have – Derek Pollard Jun 23 '22 at 18:07
  • Was tested using Firefox 101.0.1 (64-bit) so there should be no discrepancies with the Mozilla documentation at least in that regard. – nickl- Jun 28 '22 at 14:42