Why preloading matters for media

Playback start latency is one of the strongest predictors of whether a user will stick around for your video or audio content. When media begins playing almost instantly, engagement rises; when users face buffering, they abandon the page. The browser’s default behavior is to fetch media only when needed, but you can influence that with several preloading techniques depending on how your media is structured and delivered.

This article covers three approaches: the native preload attribute on media elements, declarative <link rel="preload"> fetching, and manual buffering via JavaScript. Each method has distinct trade-offs you should weigh against your use case.

Using the video preload attribute

For a single video file hosted on a server, the simplest option is the preload attribute on the <video> element. This is a hint to the browser about how much content to fetch before playback starts. Note that Media Source Extensions (MSE) streams are not compatible with the preload attribute.

Resource fetching begins only after the initial HTML document is fully loaded and parsed, which is when the DOMContentLoaded event fires. The media load event fires once the resource itself has been fetched.

Setting preload="metadata" signals that the user likely won’t need the video immediately, making it worthwhile to fetch only metadata like dimensions, track list, and duration.

<video preload="metadata">

Since Chrome 64, metadata is the default for preload, a change from the earlier auto default. By contrast, preload="auto" tells the browser it may cache enough data so complete playback can happen without stalling for more buffering.

<video preload="auto">

Because preload is only a hint, browsers can ignore it. As of this writing, Chrome applies these rules:

  • With Data Saver enabled, Chrome forces preload to none.
  • On Android 4.3, Chrome forces preload to none due to an Android bug.
  • On cellular connections (2G, 3G, 4G), Chrome forces preload to metadata.

Tips for sites with many videos

If your site hosts many videos from the same domain, set preload="metadata", or configure poster and set preload="none". Otherwise, you risk exhausting the per-domain connection limit (six under HTTP 1.1) and stalling resource loading. This practice can also improve page speed if video isn’t core to the user experience.

You can also use <link rel="preload"> to force the browser to fetch a resource without blocking the load event. These resources are stored in the browser cache and stay dormant until JavaScript, CSS, or DOM code explicitly references them.

Preload targets the current navigation, prioritizing resources by type—script, style, font, video, audio, and so on. This warms up the cache for the current session, which distinguishes it from prefetch.

Preloading a full video

To preload an entire video file, add a preload link with the correct as type. This ensures that when your JavaScript requests the video content, it can be served from cache if the preload finished in time. If the preload is still in flight, the browser falls back to a network fetch.

<link rel="preload" as="video" href="/path/to/video.mp4">

The as value for a video element is video; for an audio element, use audio.

Preloading the first segment for MSE

For segmented content delivered through Media Source Extensions, you can preload the first segment. Below is a minimal example, assuming the video is split into files like file_1.webm, file_2.webm, file_3.webm, and so on.

<link rel="preload" as="fetch" href="file_1.webm" crossorigin>

Consult MSE basics if you’re not familiar with how the MediaSource and SourceBuffer APIs work.

Detecting preload support

You can test whether a given as type is supported for <link rel="preload"> with a feature-detect snippet. Different browsers may support only certain types.

Manual buffering with MSE

When you need finer control, you can buffer media manually. This approach assumes your server supports HTTP Range requests; alternatively, it works nearly the same for file segments. Popular middleware libraries including Google’s Shaka Player, JW Player, and Video.js already encapsulate this logic.

You take over the entire buffering model, so weigh these considerations:

  • Battery awareness: Check navigator.getBattery() and level. On low battery, disable preload or drop to a lower resolution video to conserve power.
  • Data Saver mode: Look for the Save-Data client hint header. Users who opt into data-savings mode in their browser benefit from lighter media, if you can customize the experience for them.
  • Network connectivity: Inspect navigator.connection.type. If the value is cellular, you may want to avoid preloading, warn users that bandwidth could be charged, and only auto-play content that is already cached.

Pre-caching several first segments

Speculatively caching content is trickier on a page with, say, ten videos where you don’t yet know which the user will select. You have enough memory to preload one segment per video, but creating ten hidden <video> elements and ten MediaSource instances is wasteful. The Cache API provides a cleaner route.

You can intercept the network request and cache the response keyed by URL. The Cache API doesn’t yet support Range responses, so be careful using whole-file responses. Calling networkResponse.arrayBuffer() pulls the entire response into renderer memory, which argues for requests that fetch small ranges instead. For the initial pre-cache, a basic fetch-and-store will suffice.

Returning Range responses from a service worker

If you’ve stored entire video files in the Cache API, the browser will still make HTTP Range requests when seeking. The Cache API doesn’t handle Range responses, and sending the whole video to the renderer wastes memory.

In your service worker, you can intercept the Range request and construct a partial response. Use response.blob() when slicing the cached file because it hands you a file handle; response.arrayBuffer() loads the full video into memory.

One additional trick is an X-From-Cache header to flag responses served from cache. Players like Shaka Player use such a signal to discount latency when measuring network throughput, so they don’t mistake fast cache responses for fast network conditions.

For a production implementation, inspect the official Sample Media App and specifically its ranged-response.js file, which handles Range requests thoroughly.