---
title: "MP4 faststart in C#: move the moov atom to the front"
url: https://recursiveai.org/onemedia/cookbook/mp4-faststart/
description: "Move the moov atom of an MP4 to the front (faststart) in C# so the video starts playing before it has downloaded: OneMedia rewrites the box order and chunk offsets, copies every other byte unchanged, no re-encoding, no FFmpeg."
version: "0.3.0-preview.2"
---

# MP4 faststart in C#: move the moov atom to the front

An MP4 whose movie box (`moov`, the index) sits after the media data (`mdat`) can’t start playing in a browser until the end of the file has arrived. `Media.Faststart` writes a copy with `moov` in front. It is an edit, not a remux: the media data and every other box are copied as they are, and only the chunk offset tables change. The input is the Sintel trailer (CC BY 3.0, © copyright Blender Foundation | durian.blender.org), whose `moov` is at the end.

```shell
dotnet new console -o Faststart && cd Faststart
dotnet add package RecursiveAI.OneMedia --version 0.3.0-preview.2
curl -L -o sintel.mp4 https://download.blender.org/durian/trailer/sintel_trailer-480p.mp4
```

Put this in `Program.cs` and run `dotnet run`:

```csharp
using System.Buffers.Binary;
using System.Text;
using RecursiveAI.OneMedia;
using RecursiveAI.OneMedia.Remuxing;

Console.WriteLine("before: " + string.Join(" ", TopLevelBoxes("sintel.mp4")));

FaststartReport report = Media.Faststart("sintel.mp4", "sintel-faststart.mp4");

Console.WriteLine("after:  " + string.Join(" ", TopLevelBoxes("sintel-faststart.mp4")));
Console.WriteLine($"moved: {report.Relocated}, moov {report.MovieBoxBytes:N0} bytes, "
                  + $"{report.OffsetsShifted:N0} chunk offsets shifted, {report.BytesCopied:N0} bytes copied");

// The top-level boxes of an MP4: a 4-byte size and a 4-byte type each (size 1: a 64-bit size follows).
static List<string> TopLevelBoxes(string path)
{
    var boxes = new List<string>();
    using FileStream file = File.OpenRead(path);
    byte[] header = new byte[16];
    while (file.Position + 8 <= file.Length)
    {
        long start = file.Position;
        file.ReadExactly(header, 0, 8);
        long size = BinaryPrimitives.ReadUInt32BigEndian(header);
        if (size == 1) { file.ReadExactly(header, 8, 8); size = (long)BinaryPrimitives.ReadUInt64BigEndian(header.AsSpan(8)); }
        else if (size == 0) size = file.Length - start;
        boxes.Add(Encoding.ASCII.GetString(header, 4, 4));
        file.Position = start + size;
    }
    return boxes;
}
```

```text
before: ftyp free mdat moov
after:  ftyp moov free mdat
moved: True, moov 39,456 bytes, 3,688 chunk offsets shifted, 4,332,917 bytes copied
```

`moov` now follows `ftyp`; `free` and `mdat` keep their order behind it. Every chunk offset that pointed into `mdat` moved forward by the size of `moov`. A table whose offsets no longer fit 32 bits is promoted to `co64` (`report.PromotedTables`). A file that is already moov-first is copied unchanged and `Relocated` is false.

## Check it with ffprobe

FFmpeg’s trace shows the same box order:

```shell
ffprobe -v trace sintel-faststart.mp4 2>&1 | grep -o "type:'[a-z]*' parent:'root'"
```

```text
type:'ftyp' parent:'root'
type:'moov' parent:'root'
type:'free' parent:'root'
type:'mdat' parent:'root'
```

The packets are the same as the original’s, times and flags included: ffprobe lists both files’ packets with the same timestamps, sizes, flags and payload hashes.

```shell
for f in sintel.mp4 sintel-faststart.mp4; do ffprobe -v error -show_entries packet=stream_index,pts,dts,size,flags -show_data_hash MD5 -of csv $f | md5sum; done
```

```text
8484b8744e69f9447fd796647c4d56ef  -
8484b8744e69f9447fd796647c4d56ef  -
```

It still plays: in headless Chrome 149 on Linux, `sintel-faststart.mp4` loaded from a local HTTP server reached `readyState` 4 with a duration of 52.208 s, played past 2.5 s, and decoded video frames and audible audio.

## Limits

- **Progressive MP4, MOV and 3GP only.** Fragmented MP4 has no single movie box and is refused.
- **Offsets it doesn’t adjust.** HEIF-style `iloc` item locations and vendor boxes that hold absolute file offsets keep their old values.
- **Memory.** The movie box is held in memory (bounded by `MediaLimits.MaxAllocationBytes`); the media data streams through a 1 MiB buffer whatever the file size.
- **A new file.** The output must not exist. To get moov-first output while also changing the container or tracks, `Media.Remux` writes moov first by default, but it rebuilds the sample tables and drops unknown boxes; `Faststart` keeps the file as it is. Details: [Remux and faststart](https://recursiveai.org/onemedia/docs/remux/#faststart).

To check whether a browser profile plays the result, see [Check if a browser can play a video](https://recursiveai.org/onemedia/cookbook/check-browser-can-play-video/).
