Why your video won't play: containers, codecs and compatibility

The difference between a container and a codec, how to read the error you are getting, and why a remux fixes most playback problems without touching quality.

By Kyllian · Published · 7 min read

“Unsupported format” is one of the least useful error messages in computing, because the thing that failed is almost never the format. A video file is a box with several different things inside it, and any one of them can be the part your player does not understand. Knowing which one changes the fix from a twenty-minute re-encode to a five-second copy.

A file extension describes the box, not the contents

The .mp4 on the end of a filename tells you the container. A container is a specification for how to interleave one or more video streams, one or more audio streams, subtitles, chapter markers, timestamps and metadata into a single file so that a player can pull them apart again and keep them in sync. It says nothing about how the pictures were compressed.

The codec is what compressed them. H.264, H.265, VP9, AV1, MPEG-4 Part 2, ProRes: those are codecs, and they are what a decoder has to understand. The same H.264 video stream can sit inside an MP4, an MKV, a MOV or an AVI without a single bit of the picture changing.

This is why “convert my MKV to MP4” is sometimes a job that takes four seconds and sometimes a job that takes twenty minutes. If the MKV already contains H.264, the conversion is just repackaging. If it contains something the destination cannot carry, it is a re-encode.

Which part is broken, read from the symptom

The failure mode tells you where to look, and it is surprisingly reliable:

What you seeWhat is actually wrong
Player refuses to open the file at allContainer not supported
Audio plays, picture is black or greenVideo codec not supported
Picture plays, no soundAudio codec not supported
Plays locally, will not stream or start in a browserIndex at the end of the file
Plays but the seek bar does nothing usefulMissing or broken index
Picture is fine, colours on text are smearedChroma subsampling, not a fault

The middle two are the ones people misdiagnose most. A file that shows a picture with no audio is not corrupt and does not need to be re-encoded from scratch; it has an audio track in a format the player cannot decode, usually AC-3 or DTS from a rip, or Opus in a container where the player did not expect it. Fixing that means re-encoding one small stream and copying the large one.

The compatibility matrix, roughly

Support moves, and it moves in one direction, so any table like this is a snapshot rather than a law. Check the current state on caniuse.com rather than trusting a number in a blog post, including this one.

ContainerUsually containsBrowsersPhonesEditing software
MP4H.264 + AACEverywhereEverywhereEverywhere
MP4H.265 + AACPatchyiPhone yes, Android variesMostly, sometimes licensed
MOVH.264 or ProRes + AACNoApple yesYes
MKVanything at allNoRarelyVaries
WebMVP9 or VP8 + OpusYesModern onlyRarely
AVIMPEG-4 Part 2, DivXNoNoLegacy support

The row that catches people is MKV. It is an excellent container, arguably the best designed of the group, and no browser plays it. Not because there is anything wrong with the format but because nobody shipped a demuxer for it. An MKV holding perfectly ordinary H.264 video will fail in Safari, in Chrome, in a Discord embed and in most phone galleries, while the identical video stream in an MP4 plays everywhere. The MKV page goes through this case in detail; the formats reference is the wider comparison.

The other one worth knowing is H.265 inside MP4. The container is universal and the codec is not, which produces the confusing result that a file plays on the iPhone that recorded it and shows a black frame on a friend’s laptop. That is the iPhone problem in its most common form.

Find out what is actually in the file

Guessing is unnecessary. FFmpeg ships with ffprobe, and one command answers the whole question:

ffprobe -v error -show_entries stream=index,codec_type,codec_name,profile,pix_fmt,channels \
  -of default=noprint_wrappers=1 input.mkv

You get one block per stream, naming the codec for each. If codec_name=h264 comes back and the file will not play in a browser, the codec is not your problem and re-encoding the video would be twenty minutes of wasted CPU.

If you do not have FFmpeg installed, VLC shows the same information under Tools, then Codec Information, and MediaInfo shows considerably more than you need.

Remuxing: the fix that is nearly free

When the streams are fine and the box is wrong, you copy the streams into a different box. That operation is called remuxing, and it does not decode or re-compress anything:

ffmpeg -i input.mkv -map 0:v:0 -map 0:a:0 -c copy -movflags +faststart output.mp4

-c copy is the whole point: the video bits that come out are the video bits that went in, so there is no generation loss and no quality decision to make. The -map arguments take the first video and first audio track and leave everything else behind, which matters for MKVs because they frequently carry subtitle tracks in formats MP4 cannot hold. Without the maps, FFmpeg will try to bring an ASS subtitle stream into an MP4 and stop with an error that reads like the file is broken when the file is fine.

-movflags +faststart moves the index to the front. In an MP4 the moov atom holds the map of where every frame lives, and the muxer writes it at the end of the file by default because it does not know the final sizes until it has finished. A local player just reads the end of the file first and does not care. A browser streaming over HTTP cannot start until it has that index, so a file without faststart appears to hang while it downloads in full. This is the single most common reason a self-hosted video “does not work on the website” while playing fine on the desktop it was made on.

Remuxing takes seconds because it is bounded by disk speed rather than CPU. It also does not make the file any smaller, which is exactly why it is the right answer when the problem is playback and the wrong answer when the problem is size.

When only the audio is the problem

Copy the expensive stream, re-encode the cheap one:

ffmpeg -i input.mkv -c:v copy -c:a aac -b:a 192k -movflags +faststart output.mp4

Video passes through untouched, so this runs at roughly remux speed on most machines and costs you nothing visually. It is the correct fix for AC-3 and DTS tracks out of disc rips, and for the surprisingly common case of an Opus track in a container the player will not accept it from.

If the source has a 5.1 track and the destination is a phone, add -ac 2 to downmix it. Without that, FFmpeg will happily encode six channels of AAC, which is both larger than you need and another thing for the player to get wrong.

What this site can and cannot do about it

The compressor runs FFmpeg compiled to WebAssembly inside the browser tab, so the file never leaves the machine. Remux mode does exactly the stream copy described above: it reads the file, copies the video stream into the container you pick and writes it back out, typically in seconds. For an MKV or a MOV that already contains H.264 and just needs to be an MP4, that is the entire job, and the MOV page and MKV page both open the tool in that mode.

The limits are worth stating. It decodes anything FFmpeg can read. On the way out it writes H.264 or H.265 into MP4 and MOV, VP9 into WebM, any of the three into MKV, and MPEG-4 when fast mode is running against an H.264 target. Audio becomes AAC, or Opus when the container is WebM. AV1 is not in the build.

For the problem this post is about, ignore most of that. A file that will not play is a compatibility problem, and adding H.265 or VP9 to it makes the compatibility worse rather than better. Leave the codec on H.264 in an MP4 and you have the one combination nothing refuses.

Remux is also blocked when the output container is WebM, which trips people up. WebM will only hold VP8, VP9 or AV1, so there is no such thing as copying an H.264 stream into one. Remux to MKV if you want the stream untouched, or re-encode if you genuinely need WebM.

It is also slower than native FFmpeg. WebAssembly gives up some performance against compiled code, and when the browser cannot provide SharedArrayBuffer the whole thing falls back to a single-threaded core, which is slower again. How that works is its own post. For a remux none of this matters much, since almost no computation is involved. For a full re-encode it matters a great deal, and the FFmpeg comparison is honest about when to just use the command line.

Start with the probe

The cost of running ffprobe first is about two seconds, and it decides between a job that finishes before you have finished reading the output and a job that occupies a laptop fan for twenty minutes. Most of the files people bring to this problem have a perfectly good H.264 stream that has been fine the entire time, sitting in a container the destination refuses to open, or preceded by an index the destination cannot reach.

Re-encoding those files works, in the sense that a new file comes out and it plays. It also throws away a generation of quality to solve a problem that was never about quality. Find out which of the three parts is actually unsupported, then fix only that part.

Try it on your own file. The encode runs in this tab, so nothing is uploaded.

Try the compressor