Skip to content

RESOURCES / BLOG

How to Fix HTML5 Video Error Code?

Front-end devs see it all the time: a video that plays locally but fails in production, or it works in Chrome yet breaks in Safari with a cryptic “The media could not be loaded” message. The community often rallies around the same root causes, which makes this a great thread to summarize the most common fixes and a clean debugging workflow.

Hi all,
I keep running into HTML5 video issues across browsers and I see different error codes in the console or via the video element. What is the reliable, step-by-step approach for how to fix HTML5 video error code problems like MEDIA_ERR_SRC_NOT_SUPPORTED (4), NETWORK (2), or DECODE (3)? I want to know how to diagnose the root cause, fix encoding, headers, CORS, and set up fallbacks, so the video plays consistently on desktop and mobile. Any tips and sample code would be appreciated.

HTML5 video errors usually fall into four categories: aborted (1), network (2), decode (3), and source not supported (4). The fastest path to a fix is to confirm the file is encoded and served correctly, ensure the browser has a compatible source, and validate that the server is ready for partial delivery and cross-origin access.

  • Widest support: MP4 container with H.264 video and AAC audio.
  • Modern alternative: WebM with VP9 or AV1 for browsers that support it.
  • Provide multiple sources so each browser can pick what it supports. See the trade-offs in MP4 vs WebM.
<video id="player" controls preload="metadata" playsinline>
  <source src="/videos/video.mp4" type="video/mp4">
  <source src="/videos/video.webm" type="video/webm">
  Sorry, your browser does not support embedded videos.
</video>Code language: HTML, XML (xml)

If you see decode errors or Safari-only failures, re-encode and fast-start the file so play begins before full download:

# MP4 H.264 + AAC with fast start
ffmpeg -i input.mov -c:v libx264 -profile:v high -level 4.1 -pix_fmt yuv420p \
  -b:v 2500k -c:a aac -b:a 128k -movflags +faststart output.mp4

# Optional WebM (VP9 + Opus)
ffmpeg -i input.mp4 -c:v libvpx-vp9 -b:v 0 -crf 32 -c:a libopus -b:a 96k output.webmCode language: CSS (css)

For more background on the tool and its trade-offs, see FFmpeg features, use cases, pros and cons.

  • Content-Type must match the file:
  • MP4: video/mp4
  • WebM: video/webm
  • Ogg: video/ogg
  • Enable partial content so browsers can seek: set Accept-Ranges: bytes and respond with 206 for byte ranges.
# Example Node.js partial content handler
app.get('/video.mp4', (req, res) => {
  const path = 'public/video.mp4';
  const stat = fs.statSync(path);
  const range = req.headers.range;

  if (!range) {
    res.writeHead(200, { 'Content-Length': stat.size, 'Content-Type': 'video/mp4' });
    fs.createReadStream(path).pipe(res);
    return;
  }

  const [startStr, endStr] = range.replace(/bytes=/, '').split('-');
  const start = parseInt(startStr, 10);
  const end = endStr ? parseInt(endStr, 10) : stat.size - 1;
  const chunkSize = end - start + 1;

  res.writeHead(206, {
    'Content-Range': `bytes ${start}-${end}/${stat.size}`,
    'Accept-Ranges': 'bytes',
    'Content-Length': chunkSize,
    'Content-Type': 'video/mp4'
  });

  fs.createReadStream(path, { start, end }).pipe(res);
});Code language: PHP (php)

If your HTML and video are on different origins, allow cross-origin video with a permissive response header and add crossorigin to the tag if needed.

# Example response headers
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, HEAD

<video controls crossorigin="anonymous">...</video>Code language: HTML, XML (xml)

Avoid mixed content. Serve both the page and video over HTTPS.

const v = document.getElementById('player');
v.addEventListener('error', () => {
  const m = {
    1: 'MEDIA_ERR_ABORTED',
    2: 'MEDIA_ERR_NETWORK',
    3: 'MEDIA_ERR_DECODE',
    4: 'MEDIA_ERR_SRC_NOT_SUPPORTED'
  };
  const code = v.error ? v.error.code : 0;
  console.error('Video error', code, m[code]);
});Code language: JavaScript (javascript)
  • Safari requires H.264 + AAC in MP4.
  • Set muted for autoplay on mobile: <video muted autoplay playsinline>
  • Ensure audio is present or explicitly allow silent video if intended.

After you fix your pipeline, you can simplify delivery and cross-browser coverage by letting a media platform handle formats, codecs, and bitrates. For example, Cloudinary can transcode upon upload, pick the best format and quality, and deliver via a single URL. This is helpful when you manage many media assets and need consistent playback across devices.

<!-- One URL, automatic format and quality selection -->
<video controls preload="metadata" playsinline
  src="https://res.cloudinary.com/demo/video/upload/f_auto,q_auto/sample.mp4"></video>

Upload programmatically and let transformations run server-side:

import { v2 as cloudinary } from 'cloudinary';

cloudinary.config({
  cloud_name: process.env.CLOUD_NAME,
  api_key: process.env.API_KEY,
  api_secret: process.env.API_SECRET
});

// Transcoding happens in the cloud; you deliver the optimized URL
await cloudinary.uploader.upload('local/path/video.mp4', {
  resource_type: 'video',
  folder: 'videos'
});Code language: JavaScript (javascript)
  • Re-encode to MP4 H.264 + AAC and add a WebM fallback for modern browsers.
  • Serve correct MIME types, enable byte ranges, and use HTTPS.
  • Fix CORS with proper response headers and optionally add crossorigin.
  • Log the video error code at runtime to pinpoint issues.
  • Consider a managed pipeline to auto-pick formats and quality across devices.

Ready to simplify your video pipeline and deliver fast, reliable playback across every browser and device? Sign up for Cloudinary and get started for free.

Start Using Cloudinary

Sign up for our free plan and start creating stunning visual experiences in minutes.

Sign Up for Free