DEV Community

Aleksander Sekowski
Aleksander Sekowski

Posted on

A Linear VAST Tag Can Ship an MP4 and Still Be Missing Duration

A pre-roll plays on your staging player. Production Roku drops it with a generic VAST error. You diff the two environments for a week before someone opens the XML and notices there is no <Duration> anywhere in the inline.

The MP4 is there. <MediaFiles> is populated. <Impression> fires. The missing field is the one that tells the player how long the break is supposed to run.

What <Duration> is for

In VAST 2.0 and every later version, a linear creative must include <Duration> inside <Linear>. IAB VAST 2.0 ยง2.3.5.1 (carried forward in 4.x) requires it. The value is wall-clock time in HH:MM:SS or HH:MM:SS.mmm, not seconds as an integer and not mm:ss.

<Linear>
  <Duration>00:00:30</Duration>
  <MediaFiles>
    <MediaFile delivery="progressive" type="video/mp4" width="1280" height="720">
      <![CDATA[https://cdn.example.com/spot.mp4]]>
    </MediaFile>
  </MediaFiles>
</Linear>
Enter fullscreen mode Exit fullscreen mode

Players use that element before they trust the media element's clock. Skip buttons arm against <Duration> and any skipoffset on <Linear>. Companion ads schedule against it. Quartile tracking (start, firstQuartile, midpoint, thirdQuartile, complete) is keyed off the declared length, not whatever the demuxer reports after playback starts.

Wrappers do not carry duration. A three-hop wrapper chain can validate on every redirect and still terminate in an inline that forgot the field. The outer wrappers never had a <Linear> node to inspect.

How it gets omitted

The usual pattern is a pipeline that transcodes first and generates VAST second. FFmpeg knows the runtime. The trafficking export template lists <MediaFiles> and tracking URLs but treats duration as optional because the browser player "figures it out."

That works until it does not. The Google IMA SDK reads <Duration> during load. Many CTV SDKs hard-fail when it is absent. A lenient HTML5 player may start the MP4, discover 29.97 seconds at runtime, and still fire midpoint at 15 seconds against a skipoffset="00:00:05" that assumed a 30-second break. The ad plays. The measurement is wrong. Nobody gets a player error they can paste into a ticket.

Empty <Duration></Duration> is the same class of bug: the element exists but carries no parseable value. Missing entirely is what rule VAST-2.0-linear-duration catches.

This is separate from a wrong format like <Duration>00:30</Duration>. That is VAST-2.0-duration-format. Both break strict parsers. Only the missing-element case survives a quick eyeball of the creative because the video URL looks fine.

A minimal broken tag

<VAST version="4.1">
  <Ad id="spot-1">
    <InLine>
      <AdSystem>Trafficking Export</AdSystem>
      <AdTitle>Spring Sale</AdTitle>
      <Impression><![CDATA[https://track.example.com/imp]]></Impression>
      <Creatives>
        <Creative>
          <Linear skipoffset="00:00:05">
            <MediaFiles>
              <MediaFile delivery="progressive" type="video/mp4" width="1920" height="1080">
                <![CDATA[https://cdn.example.com/spring.mp4]]>
              </MediaFile>
            </MediaFiles>
            <TrackingEvents>
              <Tracking event="midpoint"><![CDATA[https://track.example.com/mid]]></Tracking>
            </TrackingEvents>
          </Linear>
        </Creative>
      </Creatives>
    </InLine>
  </Ad>
</VAST>
Enter fullscreen mode Exit fullscreen mode

Note the skipoffset with no duration to offset against. A validator that only checks XSD structure may accept this document. A spec-aware linter does not:

$ vastlint check spot.xml
spot.xml  VAST 4.1
  error    <Linear> is missing required <Duration>
           VAST-2.0-linear-duration
           /VAST/Ad[0]/InLine/Creatives/Creative[0]/Linear
Enter fullscreen mode Exit fullscreen mode

Exit code 1. One line to fix: insert <Duration>00:00:30</Duration> immediately inside <Linear>, using the actual spot length.

How I catch it before handoff

For a file still in the repo, paste or pipe it through the browser validator. It runs the same rule catalog as the CLI and links each finding to the spec citation.

For a live ad tag URL, especially one that wraps through an SSP and a DSP before the inline, the tag tester fetches the chain and shows the resolved document at the bottom. That is where the missing <Duration> lives when your starting URL is only a wrapper. Validating hop one tells you nothing about hop three.

$ vastlint check https://ad.example.com/vast?line=12345
Enter fullscreen mode Exit fullscreen mode

The CLI follows <VASTAdTagURI> redirects the same way. I keep CI on checked-in fixtures and run live URLs through the tester when a partner says "it works in our tool."

The validate VAST XML guide walks through CLI flags, severity overrides, and exit codes if you are wiring this into a pipeline. The broader IAB VAST validator guide explains what spec compliance covers versus what still requires a fetch (media 404s, wrapper depth, CDN geo blocks).

Where wrappers hide it

Wrapper documents are intentionally thin: <AdSystem>, <Impression>, <VASTAdTagURI>, maybe <Error>. No <Linear>, so no <Duration> to check until the chain resolves.

If your QA process validates only the tag URL the trafficker pasted into the ad server, you are validating the outermost hop. The inline creative at the end of the chain is a different file on a different host. A missing duration there is invisible until something follows the redirect.

That split is why "test a tag" and "validate vast xml" are different jobs in Search Console traffic. One is a live fetch problem. One is a schema problem. You need both when the failure only appears on device.

The short version

Every <Linear> needs <Duration> in HH:MM:SS. Wrappers never substitute for it. A playable MP4 does not backfill a missing element. Validate the inline, not just the wrapper you started from, before you blame the player SDK.

vastlint is independent of IAB Tech Lab. The rule id above cites the VAST spec because that is where the requirement lives, not because this is an official tool.

Top comments (0)