A Digital Video Ad Serving Template (VAST) is a standard developed by members of the IAB Digital Video Technical Standards Working Group. VAST is a Video Ad Serving Template for structuring ad tags that serve ads to video players. Using an XML schema, VAST transfers important metadata about an ad from the ad server to a video player.
It has been around since 2008 and is not a new invention and in all essence it serves the purpose well. However, there might be Ad server specific tags (extensions) or interpretations of the standard that makes it not as simple as it could be for an Ad inserter component to "speak" with different Ad server providers.
It is not uncommon that you on the Ad inserter side (whether it is server- or client-side) need to do some adaptions for each system serving the ads. To facilitate this we have developed a "proxy" that can be used in a generic way to modify or transform a VAST before it is passed on to the Ad inserter.
A VAST Transformer that is written in Typescript and can be deployed either as a service or using a server-less approach as for example as an AWS Lambda. The source code is released as open source.
Designed to be generic without having to modify the actual proxy source code when applying a new transformation, we decided to use XSLT (Extensible Stylesheet Language Transformations) as the transformation language. A language to transform XML that has been around for quite some time, and even it might seem complex at first there are a lot of documentation already written on how to use it.
Instead of having the Ad inserter to fetch the VAST XML directly from the Ad server it will instead fetch it from the VAST transformer proxy. It provides the original Ad server endpoint as an URL encoded query parameter together with an URL encoded query parameter pointing to where the XSLT to apply is available. As an example let us use our Test Adserver where you can get a VAST XML with the following URL.
https://eyevinn.adtest.eyevinn.technology/api/v1/vast?dur=60
When we URL encode it we get:
https%3A%2F%2Feyevinn.adtest.eyevinn.technology%2Fapi%2Fv1%2Fvast%3Fdur%3D60
And we want to apply this simple XSLT that just removes all ClickThrough links:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="utf-8" indent="yes"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*" />
</xsl:copy>
</xsl:template>
<!-- Drop VideoClicks -->
<xsl:template match="VideoClicks"></xsl:template>
</xsl:stylesheet>
which is available from this URL (encoded below):
http%3A%2F%2Ftestcontent.eyevinn.technology%2Fxslt%2Fremove-click-through.xsl
The Ad inserter then fetch the VAST via the VAST Transformer proxy with the following URL:
https://vast-transformer.lambda.eyevinn.technology/transform?vastUrl=https%3A%2F%2Feyevinn.adtest.eyevinn.technology%2Fapi%2Fv1%2Fvast%3Fdur%3D60&xslt=http%3A%2F%2Ftestcontent.eyevinn.technology%2Fxslt%2Fremove-click-through.xsl
The query parameter vastUrl
points to the Test Adserver and the xslt
to the location of the XSLT file.
This implementation uses the Node.js bindings of libxslt as it needs to be quite performant. Currently these Node.js bindings only works with Node versions below 15 as v16 and higher is not yet supported at the time of writing this post.
Besides that the implementation is quite straight-forward, in essence:
// fetch the XSLT XML
const xsltXml = await fetchXslt(xsltUrl);
// forward all headers except the Host header
// when fetching the VAST XML
let headers = {};
Object.keys(event.headers).forEach(k => {
if (k !== 'host') {
headers[k] = event.headers[k];
}
});
const response = await fetch(vastUrl.toString(), {
headers: headers,
});
const vastXml = await response.text();
// apply the XSLT on the VAST XML
const outXml = await xsltProcess(vastXml, xsltXml);
// gzip compress the XML
const compressed = await compress(Buffer.from(outXml, "utf-8"));
// as there are limitations in response size
// from a Lambda
return {
statusCode: 200,
headers: {
"Content-Type": "application/xml",
"Content-Encoding": "gzip",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "Content-Type, Origin",
'Access-Control-Allow-Private-Network': 'true',
},
body: compressed.toString("base64"),
isBase64Encoded: true,
}
The xsltProcess()
function mainly consists of this:
export const xsltProcess = async (sourceXml: string, xsltXml: string): Promise<string> => {
return new Promise((resolve, reject) => {
libxslt.parse(xsltXml, (err, stylesheet) => {
if (err) {
reject(err);
} else {
stylesheet.apply(sourceXml, {}, (err, result) => {
if (err) {
reject(err);
}
resolve(result);
});
}
});
});
}
And that's it!
Just for fun and to show how generic this implementation is I wrote an XSLT that transforms the VAST XML into a fully human readable HTML page.
See the result by clicking here.
About Eyevinn Technology
Eyevinn Technology is an independent consultant firm specialized in video and streaming. Independent in a way that we are not commercially tied to any platform or technology vendor.
At Eyevinn, every software developer consultant has a dedicated budget reserved for open source development and contribution to the open source community. This give us room for innovation, team building and personal competence development. And also gives us as a company a way to contribute back to the open source community.
Want to know more about Eyevinn and how it is to work here. Contact us at work@eyevinn.se!
Top comments (0)