loke.dev
Header image for Decoding Is a Hardware Budget

Decoding Is a Hardware Budget

Leverage the Media Capabilities API to detect power-efficient playback and avoid the silent battery drain of software-based video decoding.

· 3 min read

Every time a user’s laptop fan starts sounding like a jet engine during a simple video playback, a developer somewhere has ignored their hardware budget. We often treat video codecs like a buffet—if the browser says it *can* play it, we serve it—but there is a massive difference between "supported" and "sustainable."

The "Software Tax" on Battery Life

When a browser decodes video, it has two choices: use the dedicated, ultra-efficient hardware chips (the GPU or specialized ASICs) or force the CPU to do the math manually.

The latter is called software decoding. It’s like trying to calculate a massive spreadsheet by hand instead of using Excel; sure, you’ll get the answer eventually, but you’re going to be exhausted and probably need a snack. In the world of mobile devices and ultrabooks, that "exhaustion" is a drained battery and a chassis hot enough to fry an egg.

As developers, we usually just check video.canPlayType(). But that's a binary answer to a nuanced question. To actually be a good citizen of the user's hardware, we need the Media Capabilities API.

Beyond "Can it play?"

The Media Capabilities API doesn't just tell you if a format works; it tells you if it’s going to be a smooth experience and, crucially, if it’s power-efficient.

Here is what a basic query looks like:

const checkVideoBudget = async () => {
  const videoConfig = {
    type: 'media-source', // or 'file'
    video: {
      contentType: 'video/webm; codecs="vp9"',
      width: 1920,
      height: 1080,
      bitrate: 5000000,
      framerate: 60
    }
  };

  try {
    const result = await navigator.mediaCapabilities.decodingInfo(videoConfig);
    
    console.log(`Supported: ${result.supported}`);
    console.log(`Smooth: ${result.smooth}`);
    console.log(`Power Efficient: ${result.powerEfficient}`);
    
    if (result.supported && !result.powerEfficient) {
      console.warn("This will play, but it's going to eat the battery for breakfast.");
    }
  } catch (error) {
    console.error("The API might not be supported or the config is malformed.", error);
  }
};

checkVideoBudget();

Decoding is a Resource Negotiation

The powerEfficient flag is the MVP here. If supported is true but powerEfficient is false, the browser is telling you: "I can do this, but I'm going to use the CPU to do it."

I’ve seen apps force 4K AV1 video on devices that don't have AV1 hardware decoders. The result? The video stutters because the CPU can't keep up, and the user’s battery drops 10% in ten minutes.

Instead of forcing your favorite high-compression codec, you should use this API to negotiate. If AV1 isn't power efficient, maybe high-profile H.264 is. It might result in a slightly larger file size, but your user's hardware will thank you.

A Practical Implementation Strategy

You don't want to run this check every single time a video loads. Instead, run it once during the initial app load or when the network conditions change, and cache the "capabilities profile."

async function getBestVideoConfig(resolutions) {
  for (const res of resolutions) {
    const config = {
      type: 'media-source',
      video: {
        contentType: 'video/mp4; codecs="avc1.640028"', // H.264 High Profile
        width: res.width,
        height: res.height,
        bitrate: res.bitrate,
        framerate: 30
      }
    };

    const status = await navigator.mediaCapabilities.decodingInfo(config);
    
    // We prioritize power efficiency over max resolution
    if (status.supported && status.powerEfficient && status.smooth) {
      return config; 
    }
  }
  
  // Fallback to a safe baseline if nothing is "efficient"
  return baselineConfig;
}

The Gotchas

Nothing in web dev is free, and the Media Capabilities API has its quirks:

1. Bitrate Matters: Don't just guess the bitrate. If you pass an unrealistically low bitrate, the browser might claim it's efficient, only for the actual 20Mbps stream to choke the hardware later.
2. The "Smooth" Flag: This specifically refers to dropped frames. A video can be powerEfficient (using the GPU) but not smooth if the GPU itself is too weak to handle the throughput (think old integrated graphics trying to push 8K).
3. Privacy: Some browsers are a bit stingy with this info to prevent fingerprinting, though generally, the decodingInfo call is widely available in modern Chromium and WebKit browsers.

Respect the Silicon

We spend so much time optimizing our JavaScript bundles to save a few kilobytes, yet we often ignore the fact that the video tag is the single most resource-intensive element on the page.

By checking powerEfficient, you're moving beyond "it works on my machine" and into the realm of "it works for the user's life." Stop treating the CPU like a catch-all safety net and start respecting the hardware budget. Your users—and their lukewarm laps—will appreciate it.