Every MP4 file is built out of nested chunks called atoms (or "boxes" in the ISO Base Media File Format spec, if you're reading the standard). Most of them are what you'd expect: one atom holds the compressed video frames, another holds the audio samples, others hold container metadata like duration and encoder version. One atom matters more than the others for how the file behaves in a browser: the moov atom. (For the wider context of what MP4 actually is as a container, see MP4 vs MKV.)

#What the moov atom actually contains

The moov atom is the index of the file. It describes every video and audio track - how many samples they have, where each sample sits inside the file, what codec they use, how the samples are timed, and how they should be presented. Without the moov atom, a player has no idea what's in the file or how to reach any specific point in it. The pixel data is meaningless without the index that explains what it means.

Inside the index

What a player finds when it opens moov

moov The index for the whole file. Typically a few hundred kilobytes, no matter how many gigabytes of video follow it.
mvhd Movie header - overall duration, timescale, playback rate.
trak One per track. A normal file has two: video and audio.
tkhd Track header - dimensions, rotation matrix, track id.
stbl The sample table. This is the part that actually costs bytes, and the part that makes seeking possible.
stsd Which codec, and its configuration.
stts How long each sample lasts - the timing map.
stsz The size of every single sample.
stco The byte offset of every chunk. Seeking is a lookup in here.
stss Which samples are keyframes - where a seek is allowed to land.
Note what is not in here: a single pixel. The moov atom is pure bookkeeping - and without it the gigabytes of picture data sitting next to it are an unreadable blob.

The important part: the moov atom can physically sit anywhere in the file. The MP4 spec allows it to be at the front, at the tail, or (rarely) in the middle. Where the encoder chose to write it decides how the file plays.

Byte order

The same 500 MB video, written two ways

fast-start.mp4 starts in milliseconds
ftyp32 B moovthe index mdatevery frame · 500 MB
Player has everything it needs after the first few hundred kilobytes
tail-moov.mp4 stalls, or waits for the whole file
ftyp32 B mdatevery frame · 500 MB moovthe index
Nothing is playable until the reader reaches this, 500 MB in
Identical video, identical audio, identical file size to within a rounding error. The only difference is which end of the file the index was written to - and it is the difference between a video that plays and one that spins.

#Moov at the front: fast start

When the moov atom is at the very beginning of the file, a player can read the index immediately and start decoding within milliseconds of opening the file. Streaming players in browsers work this way: they request the first few kilobytes, parse the moov atom, then request the specific ranges of the video data they need - first the frames near the start, then, once the user seeks, the frames near wherever they seeked to. The user sees playback almost instantly and can jump around freely.

MP4s with the moov atom at the front are said to be fast-start, or sometimes "web-optimised." Every serious video pipeline produces them by default - YouTube, Vimeo, iOS Screen Recording, iPhone's Camera app, most modern editors. If you look at a well-produced MP4 in a hex editor, you'll see "moov" as one of the first tokens.

#Moov at the tail: the slow start

Some encoders write video data as they go, then append the moov atom at the very end of the file after they know exactly what to write into it. This is convenient for the encoder - it doesn't have to seek back to update the index while writing - but painful for anyone playing the result over a network. A browser opening a tail-moov MP4 asks for the file, reads it linearly, and finds nothing playable until the very end. It usually resolves this by requesting the tail explicitly, but not every player is smart about it. Some just stall on a black screen until the whole file has arrived.

You'll see this most often in files produced by capture tools that couldn't predict how big the video would be (screen recorders, live-encoding scripts, some low-end camera apps). The MP4 plays fine when it's on your local disk - the OS reads the tail as fast as anything else. It plays badly, or refuses to start, when it's hosted on a slow server or previewed in a browser.

Where it bites

A tail-moov file only misbehaves once it leaves your disk

Playing it frommoov at headmoov at tail
Your own hard drive Instant Instant
A server that honours byte ranges Instant Extra round trip
A server that does not Instant Waits for all 500 MB
A basic embedded player Instant Often just stalls
Upload previews on social platforms Instant Re-processed server-side
This is why the bug is so confusing to diagnose. The file is fine on the machine that made it, every time you check. It only breaks for the people you sent it to.

#Fixing it: the fast-start rewrite

Fixing a tail-moov MP4 is straightforward: rewrite the container with the moov atom moved to the head. No re-encoding is needed - this is a pure remux. The video and audio streams stay exactly the same; only the container structure changes. FFmpeg calls this -movflags +faststart, and every good converter runs it by default.

Our converter (MKV to MP4 is a good example) applies the fast-start flag to every MP4 it produces, whether the conversion is a full re-encode or a plain remux. It costs almost nothing - a second copy pass at the end of the encode - and the resulting file behaves properly in every streaming context.

#The seek-latency version of the same problem

There's a subtler failure mode. Even a fast-start MP4 can seek slowly if its moov atom uses chunk offsets without enough granularity. When you seek to a specific timestamp, the player looks up the sample at that time in the moov's index and jumps to the corresponding byte in the video data. If the index is coarse - say, one entry per second of video instead of one per frame - the player has to scan forward from the nearest known point, decoding frames it's not going to display just to catch up. This shows up as a two-second pause every time you drag the scrubber.

Well-formed MP4s use a per-sample index, which lets a player seek to any exact frame instantly. Poorly-formed ones (some early-2010s Android camera outputs, some transcode chains that dropped the fine-grained index) use per-chunk offsets and seek slowly. A fast-start rewrite doesn't automatically fix this - the moov atom's internal structure has to be rebuilt from the raw stream data, which is what a good remuxer does.

#The takeaway

If a browser takes a long time to start playing your MP4, it's almost always because the moov atom is at the wrong end of the file. The fix is a fast-start rewrite, which is a container-level operation with no quality loss. If seeking is slow even after playback starts, the moov's internal index is too coarse - a proper remux (not just a fast-start flag) rebuilds it and fixes the problem.

A few hundred kilobytes, in the wrong place, can make half a gigabyte unplayable.

The moov atom, in one sentence

The pixels and audio inside your file were never the issue. As with so much in video, the wrapper is where the story is.