Skip to content

feat(svta): SVTA2053 ad creative signaling - #331

Open
tobbee wants to merge 1 commit into
mainfrom
feat/svta2053-ad-signaling
Open

feat(svta): SVTA2053 ad creative signaling#331
tobbee wants to merge 1 commit into
mainfrom
feat/svta2053-ad-signaling

Conversation

@tobbee

@tobbee tobbee commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Implements SVTA2053 Ad Creative Signaling (payload version 2) in the DASH output, closes #310.

What it does

A new svta_ URL option marks ad-creative windows of the live timeline with an EventStream of
scheme urn:svta:advertising-wg:ad-creative-signaling, holding one Event per creative whose
node data is the v2 JSON payload — identifiers, duration, and the tracking URLs to fire while the
creative plays.

It reuses the sgai_ break-schedule grammar:

svta_( <off>:<dur>[,<off>:<dur>...] | p<period>:<dur> )[;key=val;...]

ads=<n>      creatives the break is split into, 1-20 (default 1)
skip=<s>     skipOffset in seconds on each creative
click=<0|1>  add clickThrough and a clickTracking beacon
verif=<0|1>  add an ad-verification resource
pod=<0|1>    also signal the whole break as a pod (podStart/podEnd)
ts=<n>       EventStream@timescale (default 90000)

Example: /livesim2/svta_p60:20;ads=2/testpic_2s/Manifest.mpd?sessionId=alice — two 10 s
creatives every UTC minute. As for sgai_, the video track serves the generated AD BREAK
countdown slate inside each window, so the signaled creative is visible.

The tracking URLs point back at livesim2's own /sgai/beacon endpoint with the session and break
ids baked in (a player fires them verbatim, so nothing can add them later), which makes the whole
ad-measurement round trip observable at /sgai/session_status?sid=<sessionId> — the point of
#310: end-to-end ad-measurement testing without an SSAI stack.

sgai_...;svta=1 additionally puts an SVTA2053 EventStream on every ad Period of the List MPD
returned by the ad-decisioning endpoint, describing the creative that Period imports with its real
catalog id and duration. Opt-in, because a player acting on both that and the existing callback
beacons would report each tracking point twice.

Decisions worth reviewing

  • One Event per creative, each with a single-slot payload at start: 0, per Table 5 of the
    spec. It also sidesteps a Shaka bug: svta_ad_manager.js computes
    endTime = region.startTime + slot.duration, which is wrong for any slot with start > 0.
  • VAST tracking names (impression, start, firstQuartile, midpoint, thirdQuartile,
    complete) rather than the spec's progress + offset form, because progress is not in
    Shaka's createTrackingFromEvents map and is silently dropped — nothing would fire.
  • Payload v2 only. v1 (identifiers only) is superseded and no known player needs it.
  • Node data is entity-escaped chardata, not CDATA. dash-mpd's encoder escapes " as &#34;;
    equivalent for any conformant parser, and a xml:",cdata" sibling field would duplicate the
    text on read-modify-write round trips.
  • The ad-break schedule (fixed/periodic breaks, the instances signaled in an MPD, and the slate
    window) is factored out to AdBreaks in adbreaks.go and shared with sgai_;
    sgai_slate.go becomes adbreak_slate.go. For svta_ the signaled set also keeps breaks that
    ended but are still inside the timeshift buffer, so a viewer seeking back still sees the
    signaling.

Verification

Unit tests for the grammar, the generated events and the JSON payload, plus URL-config, urlgen and
end-to-end MPD tests. Also driven through a real Shaka Player build against
svta_p20:10: ad-cue-points-changed, ad-impression and ad-started fire, and the beacons
land in the session record with per-occurrence attribution:

beacon svta-ad1 impression    evId=89412696
beacon svta-ad1 start         evId=89412696
beacon svta-ad1 firstQuartile evId=89412696
beacon svta-ad1 midpoint      evId=89412696
beacon svta-ad1 thirdQuartile evId=89412696
beacon svta-ad1 complete      evId=89412696
beacon svta-ad1 impression    evId=89412697
...

Verified against upstream Shaka 5.2.x

Both branches were re-tested against a fresh build of Shaka main
(v5.2.8-main-10-g7b41cae63) — no forked player required:

  • svta_p20:10;ads=2 — both creatives of every break report impression, start,
    firstQuartile, midpoint, thirdQuartile, complete, attributed per creative (svta-ad1,
    svta-ad2) and per break occurrence (evId).
  • sgai_p20:10;svta=1 — the Ed.6 List-MPD ad pod plays in stock Shaka (Linked Periods via
    ImportedMPD landed in 5.2.0), with the Annex I query propagated onto the callback beacons.

An earlier revision of this description claimed upstream lacked @uri and List-MPD support; that
was read from a stale checkout and is wrong. See the comment below for details and for the one
remaining open question about SVTA tracking on DASH interstitials.

New URL option svta_ marks ad-creative windows of the live timeline with an
EventStream of scheme urn:svta:advertising-wg:ad-creative-signaling, one Event
per creative carrying the SVTA2053 version 2 JSON payload as node data:
identifiers, duration, and the tracking URLs to fire while the creative plays.

It uses the same break-schedule grammar as sgai_ (svta_30:15,90:15 or
svta_p60:20), with ;ads=<n> to split a break into several creatives and
;skip=, ;click=, ;verif=, ;pod=, ;ts= for the rest of the payload. As for
sgai_, the video track serves the generated AD BREAK countdown slate inside
each window, so the signaled creative is visible.

The tracking events use the VAST names (the only ones players map; the spec's
alternative progress+offset form is silently dropped by Shaka) and point back
at livesim2's own /sgai/beacon endpoint with the session and break ids baked
in, since a player fires these URLs verbatim. The whole ad-measurement round
trip can then be watched at /sgai/session_status. Verified end to end against
a Shaka Player build: impression, start and the quartiles arrive in the
session record, attributed per break occurrence.

sgai_...;svta=1 additionally puts an SVTA2053 EventStream on every ad Period
of the List MPD returned by the ad-decisioning endpoint, describing the
creative that Period imports with its real catalog id and duration. Opt-in,
since a player acting on both that and the existing callback beacons would
report each tracking point twice. The flag rides on the ReplacePresentation
@uri so the endpoint stays stateless about the stream configuration.

The ad-break schedule (fixed or periodic breaks, the instances signaled in an
MPD, and the slate window) moves to AdBreaks in adbreaks.go, shared by sgai_
and svta_; sgai_slate.go becomes adbreak_slate.go. For svta_ the signaled set
also keeps breaks that ended but are still inside the timeshift buffer, so a
viewer seeking back still sees the signaling.
Comment thread cmd/livesim2/app/svta.go
if err != nil || n <= 0 {
return nil, fmt.Errorf("svta ts %q: must be > 0", v)
}
cfg.Timescale = uint32(n)
@avelad

avelad commented Sep 1, 2026

Copy link
Copy Markdown

InterstitialAdManager.addRegion reads attributes['url'] on
ReplacePresentation/InsertPresentation, and only reads uri on an child.
ISO/IEC 23009-1 Ed.6 §5.16.5.2 Table 63 defines @uri on AlternativeMPDEventType, and
the Annex G example writes <ReplacePresentation uri="http://cdn1.example.com/preroll.mpd" .../>.
There is no @url and no AlternativeMPD element in the spec, so livesim2's spec-conformant
events are currently dropped with "Unsupported alternative media presentation". Happy to file
this separately if it is news — it looks like a one-line fix.

Please open an issue on Shaka and I'll fix asap.

Upstream has no List-MPD support (MPD@type="list", profile:list:2024, ImportedMPD), so
the ad pod livesim2 returns today is not playable there; the event URL would have to resolve
to a plain single-period MPD.

It's supported since 5.2.0, If you find any errors, please report them and I'll fix them ASAP.

@tobbee

tobbee commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

@avelad Thanks for the update. I didn't check towards the latest version. Sorry! Will test with that instead.

@tobbee

tobbee commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

@avelad You are right on both counts, and no Shaka issue is needed — I was reading a checkout from
2026-02-05, and both points had already been fixed after that date. Sorry for the noise.

  • @uri on InsertPresentation/ReplacePresentation: fixed in 70441a336 (#9766, 2026-02-26) —
    node.attributes['uri'] || node.attributes['url'], spec form first. Moved to
    lib/ads/dash_interstitial_parser.js by 2fc8fb43c (#10292).
  • List MPDs: d701bdf3f (#10221, 2026-06-24). dash_parser.js treats type="list" as static and
    mpd_utils.js resolves ImportedMPD Linked Periods.

I built current main (v5.2.8-main-10-g7b41cae63) and ran livesim2 against it. Both work, with
no forked player:

svta_p20:10;ads=2 (this PR) — every creative of every break reports the full set:

evId=89413717  svta-ad1  impression/start/firstQuartile/midpoint/thirdQuartile/complete
evId=89413717  svta-ad2  impression/start/firstQuartile/midpoint/thirdQuartile/complete
evId=89413718  svta-ad1  impression/start/firstQuartile/midpoint/thirdQuartile/complete
...

sgai_p20:10;svta=1 — the Ed.6 List-MPD ad pod plays, the Annex I query rides onto the
callback beacons, and the SVTA EventStream we put inside the ad Periods is picked up too. Two
observations there, in case they are of interest:

  1. The SVTA set from the List MPD fires only for the first break; later breaks report through
    the callback events alone. The two are easy to tell apart by their query — SVTA URLs carry
    ?evId=…&sid=…, callback URLs carry the Annex I ?break=…&sessionId=…:

    GET /sgai/beacon/train_ad/impression?evId=89413710&sid=stock520b            <- SVTA, break 1
    GET /sgai/beacon/train_ad/firstQuartile?evId=89413710&sid=stock520b         <- SVTA, break 1
    GET /sgai/beacon/train_ad/firstQuartile?break=89413711&…&sessionId=…        <- callback, break 2
    GET /sgai/beacon/train_ad/firstQuartile?break=89413712&…&sessionId=…        <- callback, break 3
    
  2. In that interstitial case complete never fires, and impression only fires on the first
    break. With plain svta_ on the main timeline both fire reliably every time, so it looks
    specific to the interstitial player's lifecycle rather than to the payload.

Happy to open a Shaka issue for either with a one-line livesim2 repro URL — just say the word,
and I would rather ask than file noise twice in one day.

The one design question left. dash_interstitial_parser.js builds the interstitial with
tracking: null, and X-AD-CREATIVE-SIGNALING is only read on the HLS asset-list branch
(interstitial_ad_manager.js), so a DASH interstitial carries no SVTA tracking of its own. Today
livesim2 works around that by putting the SVTA EventStream inside the ad's own (List) MPD, which
is what observation 1 above exercises.

Is a DASH-side equivalent something you want in Shaka, and if so what carriage would be most useful
for livesim2 to emit as test content? The options I see are (a) keep it where it is, in the ad
MPD's Periods, (b) an SVTA EventStream in the main manifest covering the same window as the
Alternative-MPD event, or (c) something attached to the interstitial signal itself, closer to the
HLS asset-list shape. I would rather generate what is actually useful to you than guess — livesim2
can emit any of them, and I will deploy whatever we land on to livesim2.dashif.org so you have a
public stream to test against.

@avelad

avelad commented Sep 1, 2026

Copy link
Copy Markdown

Both sounds a bug on Shaka, please provide me an url to test, and I'll fix it.

Don't worry about Shaka's internal implementation :) In my opinion, the correct way to do it is to include SVTA information in each ad, not globally.

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.

Add support for SVTA2053 Ad Creative Signaling in DASH EventStream

3 participants