Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Instream video ad in a Protected Audience multi-seller auction demo #242

Merged
merged 6 commits into from
Feb 6, 2024

Conversation

kevinkiklee
Copy link
Contributor

@kevinkiklee kevinkiklee commented Feb 1, 2024

If you have any questions or comments regarding the video demo, feel free to reply to this discussion post.


This PR implements instream video ad rendering in a Protected Audience multi-seller auction.

In this demo, a pre-roll video ad has been implemented, but the same mechanism can be used to render other video ad types that use the VAST XML standard, such as mid-roll and post-roll instream video ads.

This demo shows one of the ways of handling VASTs in Protected Audience. There are other methods for Protected Audience to handle VAST XMLs, and we will publish more demos of different approaches in the future.

This PR is dependent on the multi-seller PR: #241

image

The following features have been implemented:

  • The SSP's VAST XML wraps around the DSP's VAST URI
  • A unique ID is appended to the reporting URLs in the VAST XML
  • The finalized VAST is messaged out of the creative iframe to the IMA SDK

To see the demo:

Description

In this demo, the DSP/SSP VASTs are transformed inside the creative, then post-messaged out of the iframe to provide the finalized VAST to the IMA SDK.

The video ad creative that is served contains the DSP VAST URI and the SSP's endpoint URL that transforms the VAST. From inside the creative, a request to the SSP's VAST endpoint is sent and the DSP VAST URI is included in the request. On the server, SSP wraps its VAST around the DSP's VAST URI and responds back to the creative with the finalized VAST. That VAST is post-messaged from the creative iframe to the top-level frame, and passed to the IMA SDK to render the video ad.

Sequence

  1. Buyer adds the render URLs for each SSP in the IG:
const interestGroup = {
  // ...
  ads: [
    { 
      renderUrl: 'https://privacy-sandbox-demos-dsp-a.dev/html/video-ad-creative.html?sspVastUrl=https://privacy-sandbox-demos-ssp-a.dev/vast',
      metadata: {
        adType: 'video',
        seller: 'https://privacy-sandbox-demos-ssp-a.dev/'
      }
    },
    { 
      renderUrl: 'https://privacy-sandbox-demos-dsp-a.dev/html/video-ad-creative.html?sspVastUrl=https://privacy-sandbox-demos-ssp-b.dev/vast',
      metadata: {
        adType: 'video',
        seller: 'https://privacy-sandbox-demos-ssp-b.dev/'
      }
    },
  ]
}

DSP's code for interest group config: Code

Note that this is a temporary mechanism until deprecatedReplaceInUrn() for component sellers become available in M123: WICG/turtledove#286 (comment).

After M123, the buyer can set a macro in the render URL: https://privacy-sandbox-demos-dsp-a.dev/html/video-ad-creative.html?sspVastUrl=%%SSP_VAST_URL%%.

Then in the component auction config, the seller can define the following:

const componentAuctionConfig = {
  // ...
  deprecatedReplaceInURN: {
    '%%SSP_VAST_URI%%': 'https://privacy-sandbox-demos-ssp-a.dev/vast',
  }
}

And at the render time, Protected Audience will replace the %%SSP_VAST_URI%% of the winning render URL with the value ('https://privacy-sandbox-demos-ssp-a.dev/vast') that the seller specified in the component auction config.

SSP's code for component auction config: Code

However, this functionality is not available for component sellers until M123, and therefore, in this demo, we are adding a render URL for each SSP as a temporary mechanism.

  1. In the bid generation function, the buyer has access to who the component auction seller is. This information is used to select the render URL for that SSP.

This step will be unnecessary once deprecatedReplaceInURN() is supported for component sellers.

function generateBid(browserSignals) {
  const {seller} = browserSignals;

  return {
    render: ads.find(ad => ad.metadata.seller.includes(seller))
  }
}

Bid generation code: https://github.com/privacysandbox/privacy-sandbox-demos/blob/dev/services/dsp-a/src/public/js/bidding-logic.js#L33

  1. The winning ad is chosen and the ad is rendered. The render URL contains the location of the SSP's VAST endpoint as query params.

The code inside the creative parses the SSP VAST URL and requests the finalized VAST from the SSP's server. The DSP's VAST URI and a unique ID is passed along in the request.

The SSP transforms the VAST and wraps the DSP VAST URI with the SSP VAST XML, along with appending a unique ID to each reporting URL. The response back to the creative frame contains the finalized VAST XML.

  • Video creative code for calling the SSP VAST endpoint: Code
  • SSP server code for transforming the VAST: Code
  1. The finalized VAST XML is post-messaged out of the creative frame to the video player.
  • Video creative code for post-messaging the VAST to the ad server lib: Code
  • Ad server lib code for receiving the VAST message and passing it to IMA: Code
  • IMA code for setting the VAST XML: Code

Code of interest

Publisher

Ad server (top-level seller)

Component SSP

Component DSP


Transformation

<VAST version="4.0" xmlns="https://privacy-sandbox-demos-ssp-a/vast">
  <Ad id="1234" sequence="1" conditionalAd="false">
    <Wrapper followAdditionalWrappers="0" allowMultipleAds="1" fallbackOnNoAd="0">
      <AdSystem version="4.0">Privacy Sandbox</AdSystem>
      <Error>https://privacy-sandbox-demos-ssp-a.dev/report/error?auctionId=8303603</Error>
      <Impression id="Impression-ID">https://privacy-sandbox-demos-ssp-a.dev/report/impression?auctionId=8303603</Impression>
      <Creatives>
        <Creative id="1234" sequence="1" adId="1234">
          <UniversalAdId idRegistry="Ad-ID" idValue="1234">1234</UniversalAdId>
          <Linear>
            <TrackingEvents>
              <Tracking event="start">https://privacy-sandbox-demos-ssp-a.dev/report/start?auctionId=8303603</Tracking>
              <Tracking event="firstQuartile">https://privacy-sandbox-demos-ssp-a.dev/report/firstQuartile?auctionId=8303603</Tracking>
              <Tracking event="midpoint">https://privacy-sandbox-demos-ssp-a.dev/report/midpoint?auctionId=8303603</Tracking>
              <Tracking event="thirdQuartile">https://privacy-sandbox-demos-ssp-a.dev/report/thirdQuartile?auctionId=8303603</Tracking>
              <Tracking event="complete">https://privacy-sandbox-demos-ssp-a.dev/report/complete?auctionId=8303603</Tracking>
            </TrackingEvents>
            <VideoClicks>
              <ClickThrough id="shop">
                <![CDATA[https://https://privacy-sandbox-demos-shop.dev?auctionId=8303603]]>
              </ClickThrough>
            </VideoClicks>
          </Linear>
        </Creative>
      </Creatives>
      <VASTAdTagURI>
        <![CDATA[https://pubads.g.doubleclick.net/gampad/ads?iu=/21775744923/external/single_ad_samples&sz=640x480&cust_params=sample_ct%3Dlinear&ciu_szs=300x250%2C728x90&gdfp_req=1&output=vast&unviewed_position_start=1&env=vp&impl=s&correlator=]]>
      </VASTAdTagURI>
    </Wrapper>
  </Ad>
</VAST>

@kevinkiklee kevinkiklee changed the base branch from main to multi-seller-auction February 1, 2024 18:06
@kevinkiklee kevinkiklee force-pushed the video-ad branch 3 times, most recently from a7f0710 to 6795a1c Compare February 2, 2024 02:58
@kevinkiklee kevinkiklee changed the title [WORK-IN-PROGRESS] Video ad in Protected Audience multi-seller Video ad in a Protected Audience multi-seller auction Feb 2, 2024
@kevinkiklee kevinkiklee force-pushed the video-ad branch 2 times, most recently from e7625ba to 4d27b27 Compare February 2, 2024 03:35
@Seburan Seburan requested a review from Svenmay February 2, 2024 05:18
@Seburan Seburan self-assigned this Feb 2, 2024
@Seburan Seburan added this to the v1.3 milestone Feb 2, 2024
Copy link
Contributor

@Seburan Seburan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

services/ssp-b/src/index.js Outdated Show resolved Hide resolved
Copy link
Member

@Svenmay Svenmay left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added some minor comments. Nothing major, but some clarifications seem useful from my perspective.

@patmmccann
Copy link

patmmccann commented Feb 2, 2024

You should add to this demo or make an independent demo showing publisher video content running after the video ad. Video ad is quite vague; most people refer to ads with content, but this appears to be in banner video. The piece we're curious to see is analogous to this https://googleads.github.io/googleads-ima-html5/vsi/ . Am I reading this accurately? Can the postmessage out of the frame support pre-roll? If so lets make that crystal.

@kevinkiklee
Copy link
Contributor Author

kevinkiklee commented Feb 2, 2024

You should add to this demo or make an independent demo showing publisher video content running after the video ad. Video ad is quite vague; most people refer to ads with content, but this appears to be in banner video.

Hi @patmmccann! Yes, this is a preroll ad and the publisher's video will play after the preroll ad finishes. This is where the publisher's video is: https://github.com/privacysandbox/privacy-sandbox-demos/blob/video-ad/services/news/src/views/index.ejs#L42

Am I reading this accurately? Can the postmessage out of the frame support pre-roll? If so lets make that crystal.

Yes, the finalized VAST XML (DSP+SSP VASTs) is post-messaged out a Protected Audience ad iframe to the top-level frame to support a preroll ad.

The piece we're curious to see is analogous to this https://googleads.github.io/googleads-ima-html5/vsi/ .

I've posted the finalized VAST XML that is post-messaged out of the iframe in the descriptions. You can copy-paste that XML into that VAST XML text box on the IMA inspector page to see the preroll ad.


Also, we are in the process of trying to deploy this ASAP, and we are targeting sometime next week. However, you can pull down the branch and play around with it yourself locally.

@michaelkleber
Copy link

michaelkleber commented Feb 2, 2024

Hey @kevinkiklee This is a great demo!

I think we should also put together an example of this same idea but where the SSP (component seller) does the work, and the DSP buyer's ad server just serves the same XML bytes that they have always served.

Seems like the easiest way to support that would be for the renderURL itself to be the thing that changes. Instead of the Interest Group carrying around a renderURL like buyer.com/my-vast-here.xml, they could change to a renderURL like component-seller.com/vast-wrapper.html?creative=buyer.com%2Dmy-vast-here.xml.

Edit to add: This would mean the IG needs to carry around different renderURLs for the same ad served through each different SSP the DSP buys through; maybe SSPs can cooperate to reduce that overhead. But let's at least have a demo that shows how to do this with a single SSP and DSP pair, and leave the multiple-buying-paths question for another day.

@patmmccann
Copy link

@kevinkiklee thanks for clarifying! I recommend you re-title the PR 'Video Pre-roll ad...'

Nice work!

@patmmccann
Copy link

patmmccann commented Feb 2, 2024

cc @karimMourra @fowler446

@kevinkiklee kevinkiklee changed the title Video ad in a Protected Audience multi-seller auction Preroll video ad in a Protected Audience multi-seller auction Feb 2, 2024
@kevinkiklee
Copy link
Contributor Author

I think we should also put together an example of this same idea but where the SSP (component seller) does the work, and the DSP buyer's ad server just serves the same XML bytes that they have always served.

@michaelkleber Sounds good. I'll get that demo started once I wrap up the open PRs.

SSP-supplied Render URL approach

  1. The DSP sets the following render URL in the IG:
const interestGroup = {
  // ...
  ads: [
    { 
      renderUrl: 'https://privacy-sandbox-demos-ssp-a.dev/video-ad.html?dspVastUri=https://privacy-sandbox-demos-dsp-a.dev/preroll.xml',
      // ...
    }
  ]
}

The render URL points to the SSP’s video ad serving endpoint, and the DSP’s VAST URI is added as query params.

The SSP is now responsible for serving the actual ad that is rendered inside the iframe, and it also contains the finalized VAST XMLs (SSP+DSP VAST XMLs).

  1. When that ad wins the auction, the browser makes a request to the render URL which is the SSP's ad serving endpoint /video-ad.html with the DSP's VAST URI set in the query params
  2. The SSP’s /video-ad.html endpoint responds with the HTML document that contains the finalized VAST XML
  3. SSP’s HTML document is rendered in the ad iframe
  4. The finalized VAST XML is post-messaged out of the creative frame to the video player

@michaelkleber
Copy link

  1. The SSP’s /video-ad.html endpoint responds with the HTML document that contains the finalized VAST XML

That seems like a very reasonable implementation, though another choice would be for the SSP's video-ad.html code to fetch the VAST XML directly from the browser and do the wrapping browser-side. I don't know which approach SSPs would like better; it seems to me like they would both work.

@kevinkiklee
Copy link
Contributor Author

That seems like a very reasonable implementation, though another choice would be for the SSP's video-ad.html code to fetch the VAST XML directly from the browser and do the wrapping browser-side.

Yea, client-side transformation is definitely doable. For the demo, wrapping on the client-side would be easier to implement, so I'll just go with that now, but I can easily switch the implementation if I receive feedback that SSPs prefer server-side processing.

@kevinkiklee kevinkiklee changed the title Preroll video ad in a Protected Audience multi-seller auction Instream video ad in a Protected Audience multi-seller auction demo Feb 3, 2024
@adamleslie
Copy link

Hi, how does this support video fallback? https://support.google.com/admanager/answer/3007370?hl=en

This is a highly used feature in video advertising for publishers

@kevinkiklee
Copy link
Contributor Author

Hi, how does this support video fallback? https://support.google.com/admanager/answer/3007370?hl=en

This is a highly used feature in video advertising for publishers

@adamleslie Hi there! The goal of this demo is to show one way to get VAST output from a Protected Audience auction, and the VAST XML can include the fallback video ad information: https://support.google.com/admanager/answer/4517679#:~:text=Fallback%2C%20also%20known%20as%20%22waterfall%22

Then, the SDK implements the fallback logic based on this VAST, like the GAM video fallback flow you linked to.

Base automatically changed from multi-seller-auction to dev February 5, 2024 17:21
@adamleslie
Copy link

adamleslie commented Feb 5, 2024

Hey, thank you. Just to confirm; the VAST output from a Protected Audience top-level auction would come back as essentially a stitched playlist of multiple VAST XML documents from the various Protected Audience component auctions.

The playlist of multiple VAST XML documents would be handled by the renderer, e.g. the IMA SDK - as is done today.

Is that correct?

Just to confirm.. In the posed scenario I may have 5 or more completely different component auction demand sources expected to be written into a single playlist for delivery and this would also need to include direct advertising which may not be using any Privacy Sandbox components - how all that functions in parallel is not super clear to me

@michaelkleber
Copy link

Hi @adamleslie: No, the Protected Audience auction only allows a winner to come from a single Interest Group buying through a single component auction. Since this demo is based on using the Protected Audience API in the form that is already available in Chrome today, it cannot combine things across multiple IGs.

@rdgordon-index
Copy link

Curious about the transition from an opaque URN to VAST XML that can be loaded by a player -- neither XMLHTTPRequest nor fetch() seem to be able to work with URNs.

@michaelkleber
Copy link

Curious about the transition from an opaque URN to VAST XML that can be loaded by a player -- neither XMLHTTPRequest nor fetch() seem to be able to work with URNs.

The method used in Kevin's demo is for the URN, which renders inside an iframe, to cause the XML to load within the rendering iframe and then get postMessage'd out (after some wrapping or transformation). The variants have to do with who takes care of the JS/HTML to load/transform/postMessage the XML: it could be in the hands of the buyer, or the component seller server-side, or the component seller browser-side.

@kevinkiklee kevinkiklee merged commit 042da90 into dev Feb 6, 2024
2 checks passed
@kevinkiklee kevinkiklee deleted the video-ad branch February 6, 2024 14:03
@adamleslie
Copy link

adamleslie commented Feb 8, 2024

Ok, so lots of demand goes into the top-level auction; only one demand source comes out of the top-level auction - this would be an issue if that understanding is correct.

Summarising, moving to this model may have notable impacts to video advertising fill for Publishers - up to say 50% less fill and thus revenues.

Detail:
Current video advertising operations behave differently from display due to the expected functionality of VAST. This is to say that even in a scenario where you run auctions before trying to render (say through Prebid), buyers commonly run additional logic (say brand safety checks) at the point of time when the player tries to render the underlying mediafile from the VAST XML document. Today, this is not so big of an issue as most Publishers tend to populate several VAST XMLs into a playlist (say 5 per video opportunity) for the player to mediate through using VAST errors; however in a scenario where this is reduced to one there is a greater chance that it errors and causes a missed video ad opportunity.

Furthermore,
Considering the above point and integration with wider demand (say direct advertising campaigns which are VAST redirects or holding group multi deal IDs), it's not obvious how or when these PAAPI components from the top level auction are included into the render process (read: VAST playlist which typically is the response object to the player from the ad server) for the IMA SDK.

In short,

  • It would be good to explore including more components from the top-level auction understanding the current behaviours of video advertising in practice (only for video)
  • It would be good to understand in greater detail how this integrates into the wider advertising operations, specifically technologies such as the Google IMA SDK

If you have any further questions on the points I've made - let me know!

@kevinkiklee
Copy link
Contributor Author

Hi @adamleslie. We have started a discussion post for the video ads here: #254.

I've migrated your comment and will respond there.

@kevinkiklee
Copy link
Contributor Author

If anyone has any comments or questions regarding the video ad demo, please use the discussion post: Demo: Instream video ad in a Protected Audience sequential auction setup

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants