Zze Zze - 4 years ago 176
Javascript Question

html5 video & audio cache issue

I've written a custom media preloader which uses a series of

XMLHttpRequests
to load large amounts of media prior to displaying my
ng2 app
. It is a stakeholder requirement that all media is downloaded in full prior to use of the app.

private loadFile(media: any) {
return new Promise(function (resolve, reject) {
var error: boolean = false;

//for (var media of media.videos) {
//TODO: Check how this loads.....
//console.log("Now Loading video >> ", media, media.hasOwnProperty("path"));


// Standard XHR to load an image
var request = new XMLHttpRequest();
request.open("GET", (<any>media).path);
request.responseType = 'blob';

// When the request loads, check whether it was successful
request.onload = () => {
if (request.status === 200) {
resolve(request.response);
}
else
// If it fails, reject the promise with a error message
reject(Error('Media didn\'t load successfully; error code:' + request.statusText));
};

// If an error occurs
request.onerror = () => {
// Also deal with the case when the entire request fails to begin with
// This is probably a network error, so reject the promise with an appropriate message
reject(Error('There was a network error.'));
};

request.onreadystatechange = function () {
if (request.readyState == 4) {
console.log("Load Complete >> ", media, "from", request.status); // Another callback here
}
};

// Every tick of the progress loader
request.onprogress = data => console.log(data);

// Send the request
request.send();
})
}


It works great and successfully loads in all the media that I feed it.

I only have 1 issue and that is in Chrome, when I reference a
<video>
or
<audio>
which has been pre-loaded,, it doesn't pull it from the cache, it instead re-downloads it from the server. (IE9 even pulls from cache)



Any audio and video elements will always re-download from the server...

<video width="640px" height="auto" controls autoplay preload="auto">
<source src="./app/assets/video/Awaiting%20Video%20Master.mov" type="video/mp4"/>
</video>

<audio controls autoplay preload="auto">
<source src="./app/assets/audio/1_2_4_audio1.mp3" type="audio/mp3" />
</audio>


This will always load from cache...

<img src="./app/assets/images/BTFG-BOLD_Fundamentals.png" />





Here are 2 screenshots, one from chrome and one from edge showing the network activitiy from the dev tools (both had their caches reset prior to launch)...

Chrome
enter image description here

Edge
enter image description here

The main difference that I notice is the request status is different between browsers when it comes to rendering the content (post preloading). But why is this the case?

I found this SO post from 2013 which states that:


How video is buffered is browser implementation dependent and therefor may vary from browser to browser.

Various browsers can use different factors to determine to keep or to discard a part of the buffer. Old segments, disk space, memory and performance are typical factors.


Is this what is happening here? And if so, does anyone know a way to fix this so that chrome always attempts to pull the videos from cache?

Answer Source

Not sure if the caching issue is a chrome bug, but what you do seems really odd to me.

You are preloading your media, or actually, downloading it entirely, but then set the mediaElement to the original source.

When we load media through a mediaElement (<audio> or <video>), the browser will make range requests, i.e, it won't download the full file, but only what it needs to play without interruption.
That's why you get 206 Partial content responses. It's also probably why chrome doesn't recognize it as being the same requests, and hence doesn't use the cache once again I'm not sure if it's a chrome bug or not.

But since you already did download the full file, why don't you set your mediaElement's src to this downloaded file ?

// since you are setting the hr reponseType to `'blob'`
mediaElement.src = URL.createObjectURL(request.response);
// don't forget to URL.revokeObjectURL(mediaElement.src) when loaded

Working example : (which triggers a weird bug on my FF...)

function loadVideo(url) {
  return new Promise((resolve, reject) => { // here we download it entirely
      let request = new XMLHttpRequest();
      request.responseType = 'blob';
      request.onload = (evt)=>resolve(request.response);
      request.onerror = reject;
      request.open('GET', url);
      request.send();
    }).then((blob)=> 
    	new Promise((resolve, reject)=>{
    		resolve(URL.createObjectURL(blob)); // return the blobURL directly
    		})
    	);

}

loadVideo('https://dl.dropboxusercontent.com/s/bch2j17v6ny4ako/movie720p.mp4')
  .then(blobUrl => { // now it's loaded
    document.body.className = 'loaded';
    let vid = document.querySelector('video');
    vid.src = blobUrl; // we just set our mediaElement's src to this blobURL
    vid.onload = () => URL.revokeObjectURL(blobUrl);
  }).catch((err) => console.log(err));
video{
  display: none;
  }
.loaded p{
  display: none;
  }
.loaded video{
  display: unset;
  }
<p>loading.. please wait</p>
<video controls></video>

Or with the fetch API :

function loadVideo(url) {
  return fetch(url)
    .then(resp => resp.blob())
    .then(blob => URL.createObjectURL(blob));
}

loadVideo('https://dl.dropboxusercontent.com/s/bch2j17v6ny4ako/movie720p.mp4')
  .then(blobUrl => { // now it's loaded
    document.body.className = 'loaded';
    let vid = document.querySelector('video');
    vid.src = blobUrl; // we just set our mediaElement's src to this blobURL
    vid.onload = () => URL.revokeObjectURL(blobUrl);
  }).catch((err) => console.log(err));
video {
  display: none;
}
.loaded p {
  display: none;
}
.loaded video {
  display: unset;
}
<p>loading.. please wait</p>
<video controls></video>

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download