Manifest manipulator for VOD streams

The Pod Serving API provides access to adaptive-bitrate video ad pods prepared in such a way that they can be stitched directly into a user-facing HLS or MPEG-DASH media playlist.

This guide is focused on implementing a basic Pod Serving manifest manipulation server for VOD streams.

Receive stream manifest requests

Your manifest manipulator must provide an API endpoint to listen for manifest requests from the video player client app. At minimum, this endpoint must collect a stream ID from the client player app. This stream ID is used to identify the streaming session to Ad Manager in your ad pod requests.

You also need to collect some other information to identify the appropriate content stream, for example, a content ID.

Example manifest request endpoint

GET /api/stream_id/{stream_id}/video/{content_id}.{format}
Host: {your_domain}
Path parameters
stream_id The Ad Manager stream ID from the client video player app.
content_id A hypothetical ID corresponding to the content video in your system.
format A hypothetical parameter corresponding to the stream format. One of either:
mpd For MPEG-DASH streams
m3u8 For HLS streams

Retrieve the content stream

Use the content ID collected from the manifest request to select the content stream to stitch with ads.

Request ad pod manifests

To request ads from Ad Manager, your server must make a POST request to the ad pods endpoint, passing the requested encoding profiles, ad tag, and targeting parameters. This request also includes the stream ID that you collected in Step 1.

In return, you receive a list of ad pod objects containing manifest files for the ad pods requested by the publisher's ad tag and information about when and where they should be inserted into your content.

POST /ondemand/pods/api/v1/network/{network_code}/streams/{stream_id}/adpods
Host: dai.google.com
Content-Type: application/json
Path parameters
network_code The publisher's Ad Manager 360 network code.
stream_id The stream ID from the client video player app.

JSON body

Body parameters
encoding_profiles Required A list of JSON representations of the encoding profiles you want to receive for each ad break. See details below

To make playback as seamless as possible, this should match the set of encoding profiles used in your content stream.

ad_tag Required An ad tag to request VMAP ads.
cuepoints Optional A list of cue points within the content stream where midroll ad breaks will be inserted. Cue points are measured in floating point seconds.

Required only for VMAP responses that contain mid-rolls using positional time offsets. This is uncommon.

content_duration_seconds Optional The content duration in seconds.

Required only for VMAP responses that contain mid-rolls using percentage time offsets. This is uncommon.

manifest_type Optional The format of the ad streams being requested, either hls or dash. The default value is hls.
dai_options Optional Additional options controlling aspects of how the manifests are rendered. See details below
Encoding profile
profile_name Required An identifier for this Encoding Profile. This value can be any string you choose, but you cannot have multiple encoding profiles with the same name on the same stream.
type Required The encoding type of the stream described by this encoding profile. Content types are: media, iframe, subtitles.
container_type Required The container format used by this encoding profile. Container formats are: mpeg2ts, fmp4cmaf, hls_packed_audio
video_settings Optional Required if the encoding profile type is iframe. Otherwise, only allowed if the media type contains video. See details below
audio_settings Optional Required if the encoding profile contains audio. Only allowed if the type is media. See details below
subtitle_settings Optional Required if the encoding profile contains subtitles. See details below
Video settings
codec Required The RFC6381 codec string.

Example: avc1.4d000c

bitrate Required An integer representing the max video bitrate of this profile in bytes per second.
frames_per_second Required The floating point FPS of the video.
resolution Required A JSON-encoded value containing video `width` and `height` in pixels.

Example: {"width": 640, "height": 320}

Audio settings
codec Required The RFC6381 codec string.

Example: mp4a.40.5

bitrate Required An integer representing the max audio bitrate of this profile in bytes per second.

Example: 300000

channels Required An integer representing the number of audio channels including low frequency channels.
sample_rate Required An integer representing the audio sampling rate in hertz.

Example: 4800

Subtitle settings
format Required The file format used by in-band subtitles. Supported values are webvtt or ttml.
language Optional The subtitle language as an RFC5646 language string. If provided, this value is only used for DASH rendering.

Example: en-us

DAI options
dash_profile Optional The MPEG-DASH profile to apply to ad pod manifests. This setting is used for DASH manifests only. Allowed values are live or on-demand. The default value is on-demand.

The value live corresponds to the MPEG-DASH profile "urn:mpeg:dash:profile:isoff-live:2011".

The value on-demand corresponds to the MPEG-DASH profile urn:mpeg:dash:profile:isoff-on-demand:2011.

ad_pod_timeout Optional The maximum time to spend selecting ads and building ad pods, in floating point seconds. After this time has elapsed, Ad Manager returns any ads already selected in the ad_pods response and stops processing.
sam_id Optional Specifies an alternate debug key that can be used to look up sessions in the stream activity monitor.

Response

Response parameters
valid_for Duration for which these ad pod playlists are valid in dhms (days, hours, minutes, seconds) format.
valid_until The date and time until which these ad pod playlists are valid as an ISO8601 datetime string, in yyyy-MM-dd'T'hh:mm:ss.sssssssss[+|-]hh:mm format.
ad_pods A list of ad pods selected for this stream.
Ad pod
manifest_uris For HLS streams only. A map of encoding profile IDs to HLS manifest URIs.
mpd_uri For DASH streams only. The URI of the DASH MPD.
type The type of ad pod. Ad pod types are: pre, mid, or post.
start For mid-roll ad pods only. The position in the stream where this ad pod should be inserted, in floating point seconds.
duration The duration of this ad pod in floating point seconds.
midroll_index For mid-roll ad pods only. The index of the current mid-roll ad pod. Indexing begins with 1.

Example request (cURL)

curl -X POST \
     -d '@request-body.json' \
     -H 'Content-Type: application/json' \
  https://dai.google.com/ondemand/pods/api/v1/network/21775744923/streams/6e69425c-0ac5-43ef-b070-c5143ba68541:CHS/adpods

Example request body

This is the contents of request_body.json referenced in the cURL call above.

{
  "encoding_profiles": [
   {
     "profile_name": "1080p",
     "type": "media",
     "container_type": "mpeg2ts",
     "video_settings": {
       "codec": "avc1.4d000c",
       "bitrate": 5000000,
       "frames_per_second": 30.0,
       "resolution": {
         "width": 1920,
         "height": 1080
       }
     },
     "audio_settings": {
       "codec": "mp4a.40.5",
       "bitrate": 300000,
       "channels": 2,
       "sample_rate": 48000
     }
   },
   {
     "profile_name": "360p",
     "type": "media",
     "container_type": "mpeg2ts",
     "video_settings": {
       "codec": "avc1.4d000d",
       "bitrate": 1000000,
       "frames_per_second": 30.0,
       "resolution": {
         "width": 640,
         "height": 360
       }
     },
     "audio_settings": {
       "codec": "mp4a.40.5",
       "bitrate": 64000,
       "channels": 2,
       "sample_rate": 48000
     }
   },
   {
     "profile_name": "subtitles-webvtt",
     "type": "subtitles",
     "subtitle_settings": {
       "format": "webvtt"
     }
   }
 ],
 "ad_tag": "https://pubads.g.doubleclick.net/gampad/ads?...",
 "manifest_type": "hls"
}

Example response

{
  "valid_for": "8h0m0s",
  "valid_until": "2023-03-24T08:30:26.839717986-07:00",
  "ad_pods": [
    {
      "manifest_urls":{
        "1080p": "https://{...}/pod/0/profile/1080p.m3u8",
        "360p": "https://{...}/pod/0/profile.m3u8",
        "subtitles-webvtt": "https://{...}/pod/0/profile/subtitles-en.vtt"
      },
      "type": "pre",
      "duration": 10.0
    },
    {
      "manifest_urls":{
        "1080p": "https://{...}/pod/1/profile/1080p.m3u8",
        "360p": "https://{...}/pod/1/profile.m3u8",
        "subtitles-webvtt": "https://{...}/pod/1/profile/subtitles-en.vtt"
      },
      "type": "mid",
      "start": 15.0,
      "duration": 15.0,
      "midroll_index": 1
    },
    {
      "manifest_urls":{
        ]"1080p": "https://{...}/pod/2/profile/1080p.m3u8",
        "360p": "https://{...}/pod/2/profile.m3u8",
        "subtitles-webvtt": "https://{...}/pod/0/profile/subtitles-en.vtt""
      },
      "type": "post",
      "duration": 10.0
    }
  ]
}

Stitch ad pods into content

The process of stitching ad pods into your content streams varies depending on your implementation, the stream format, and what features you choose to implement from the format's specifications. The following workflows are suggestions for how to handle this process. The precise details of your implementation might vary, based on your business needs and your content streams.

HLS streams

If you're stitching a stream in the HLS format, your content stream will be a multivariant playlist of links to separate stream manifests, one for each encoding profile. Your ad pods needs to be inserted into each of these variant manifests. One way to do this is to prepare all the variant manifests and pass them to a Content Delivery Network (CDN) for hosting. The final multivariant playlist is a set of links to these CDN-hosted manifests.

Iterate over encoding profiles

For each encoding profile, gather all the associated ad pod manifests from Ad Manager's response, along with their associated start times. For pre-roll ad pods, set the start time to 0. For post-rolls, use the content's duration as the ad pod's start time. Identify the variant stream in the multivariant playlist that matches each encoding profile's audio and video settings.

Example ad pods array
"ad_pods": [
    {
      "manifest_urls":{
        "1080p": "https://{...}/pod/0/profile/1080p.m3u8",
        "360p": "https://{...}/pod/0/profile/360p.m3u8",
        "subtitles-en": "https://{...}/pod/0/profile/subitles-en.vtt"
      },
      "type": "pre",
      "duration": 10.0
    },
    {
      "manifest_urls":{
        "1080p": "https://{...}/pod/1/profile/1080p.m3u8",
        "360p": "https://{...}/pod/1/profile/360p.m3u8",
        "subtitles-en": "https://{...}/pod/1/profile/subitles-en.vtt"
      },
      "type": "mid",
      "start": 15.0,
      "duration": 15.0,
      "midroll_index": 1
    },
    {
      "manifest_urls":{
        "1080p": "https://{...}/pod/2/profile/1080p.m3u8",
        "360p": "https://{...}/pod/2/profile/360p.m3u8",
        "subtitles-en": "https://{...}/pod/2/profile/subitles-en.vtt"
      },
      "type": "post",
      "duration": 10.0
    }
  ]
Example multivariant content playlist
#EXTM3U
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs0",LANGUAGE="en",NAME="English",AUTOSELECT=YES,DEFAULT=YES,URI="https://{...}/subitles-en.vtt"
#EXT-X-STREAM-INF:BANDWIDTH=5000000,RESOLUTION=1920x1080,CODECS="avc1.4d000c,mp4a.40.5"
https://{...}/1080p.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=1000000,RESOLUTION=640x360,CODECS="avc1.4d000d,mp4a.40.5"
https://{...}/360p.m3u8
Example collected variant data
Encoding profile: "1080p"
Profile settings: {...}
Content manifest: https://{...}/1080p.m3u8
Ad pods (start time -> manifest):
    0 -> https://{...}/pod/0/profile/1080p.m3u8
   15 -> https://{...}/pod/1/profile/1080p.m3u8
  600 -> https://{...}/pod/2/profile/1080p.m3u8

Insert ads into each variant manifest

For each variant stream, go through the content manifest's segments, keeping a running total of the elapsed content time. When you come to the start position of an ad pod, extract the list of segments from the ad pod's manifest, wrap the list of segments in two #EXT-X-DISCONTINUITY tags, and insert the list at the current location in the content manifest. Continue this process until all ad pods and variant streams have been processed.

The resulting manifests must conform to the HLS standard. Therefore, depending on which features of the specification your content manifest incorporates, you might need to make a final pass over the combined manifest to fix media sequence numbers, content duration, discontinuity sequence numbers, and any other tags that need to be updated to take the new ad segments into account. Once any discrepancies with the standard have been repaired, push each user-specific variant manifest to your CDN for hosting.

If your content manifest is encrypted, you need to store the last encryption key found before the start of the current ad pod in an #EXT-X-KEY tag. Then, you need to add the tag #EXT-X-KEY:METHOD=NONE to remove encryption before the first segment of each ad pod. Finally, you must add a copy of the stored #EXT-X-KEY tag before the first segment of content after each ad pod, to restore the content encryption.

Example collected variant data
Encoding profile: "1080p"
Content manifest: https://{...}/1080p.m3u8
Ad pods (start time -> manifest):
    0 -> https://dai.google.com/{...}pod/0/profile/1080p.m3u8
   15 -> https://dai.google.com/{...}pod/1/profile/1080p.m3u8
  600 -> https://dai.google.com/{...}pod/2/profile/1080p.m3u8
Example content manifest

This is the content of the https://{...}/1080p.m3u8 manifest listed in the collected variant data.

#EXTM3U
{...}
#EXTINF:5.000,
https://{...}/1080p/content-segment-0.ts
#EXTINF:5.000,
https://{...}/1080p/content-segment-1.ts
#EXTINF:5.000,
https://{...}/1080p/content-segment-2.ts
#EXTINF:5.000,
https://{...}/1080p/content-segment-3.ts
#EXTINF:5.000,
https://{...}/1080p/content-segment-4.ts
#EXTINF:5.000,
https://{...}/1080p/content-segment-5.ts
{...}
Example ad pod manifest

This is the contents of the https://dai.google.com/{...}/pod/1/profile/1080p.m3u8 manifest listed in the collected variant data.

#EXTM3U
{...}
#EXTINF:5.000,
https://dai.google.com/{...}/0.ts
#EXTINF:5.000,
https://dai.google.com/{...}/1.ts
#EXTINF:5.000,
https://dai.google.com/{...}/2.ts
Example stitched variant manifest

This would be the resulting stitched variant manifest, passed to the CDN and hosted at https://cdn.{...}/{userid}/1080p.m3u8.

#EXTM3U
{...}
#EXTINF:5.000,
https://{...}/1080p/content-segment-0.ts
#EXTINF:5.000,
https://{...}/1080p/content-segment-1.ts
#EXTINF:5.000,
https://{...}/1080p/content-segment-2.ts
#EXT-X-DISCONTINUITY
#EXTINF:5.000,
https://dai.google.com/{...}/0.ts
#EXTINF:5.000,
https://dai.google.com/{...}/1.ts
#EXTINF:5.000,
https://dai.google.com/{...}/2.ts
#EXT-X-DISCONTINUITY
#EXTINF:5.000,
https://{...}/1080p/content-segment-3.ts
#EXTINF:5.000,
https://{...}/1080p/content-segment-4.ts
#EXTINF:5.000,
https://{...}/1080p/content-segment-5.ts
{...}

Build multivariant playlist

Collect the CDN addresses for each completed variant manifest, along with the matching encoding profile details, and assemble the results into a new multivariant manifest. This user-specific manifest is returned as the response to the manifest request that you received in Step 1.

Example final multivariant playlist
#EXTM3U
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs0",LANGUAGE="en",NAME="English",AUTOSELECT=YES,DEFAULT=YES,URI="https://cdn.{...}-subitles-en.vtt"
#EXT-X-STREAM-INF:BANDWIDTH=5000000,RESOLUTION=1920x1080,CODECS="avc1.4d000c,mp4a.40.5"
https://cdn.{...}/{userid}/1080p.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=1000000,RESOLUTION=640x360,CODECS="avc1.4d000d,mp4a.40.5"
https://cdn.{...}/{userid}/360p.m3u8

MPEG DASH streams

If you are stitching a stream in the MPEG DASH format, you only need to produce a single file. This can make DASH streams easier to stitch than HLS.

A properly prepared MPEG DASH media presentation description (MPD) file should consist of several periods, each containing multiple representations. Each representation should match one of your encoding profiles. Each ad pod returned from Ad Manager is also an MPD file containing a sequence of periods with matching representations.

To stitch these MPD files together, start by taking note of the start times for each ad pod. For pre-roll, insert the preroll ad pod periods before any content period. For post-rolls, insert the postroll ad pod periods after all the content periods. Iterate over the periods in the content MPD, keeping track of the elapsed playtime for all processed content periods. When you reach a boundary between periods that corresponds to an ad pod's start time, insert the periods from the matching mid-roll ad pod's MPD file at that boundary.

The final stitched MPD file must fully conform to the MPEG_DASH specifications, so you might need to iterate over the final file one more time correcting any period start times, fixing the media presentation duration to account for the newly inserted ad periods, and resolving any other conflicts that could have arisen from the stitching process.

Example content MPD

<?xml version="1.0" encoding="UTF-8"?>
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" minBufferTime="PT1.500000S" type="static" mediaPresentationDuration="PT0H10M00.000S" profiles="urn:mpeg:dash:profile:isoff-on-demand:2011">
  <ProgramInformation moreInformationURL="http://.../info">
    <Title>Example Stream</Title>
  </ProgramInformation>
  <Period duration="PT0H0M15.000S" id="content-period-1">
    ...
  </Period>
  <Period duration="PT0H0M15.000S" id="content-period-2">
    ...
  </Period>
  <Period duration="PT0H0M15.000S" id="content-period-3">
    ...
  </Period>
  ...
</MPD>

Example ad pod JSON

[{
  "mpd_uri": "https://{...}pod/1.mpd",
  "type": "mid",
  "start": 15.0,
  "duration": 15.0,
  "midroll_index": 1
}]

Example ad pod MPD

This is the content of the mpd_uri from the ad pod JSON above.

<?xml version="1.0" encoding="UTF-8"?>
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" minBufferTime="PT1.500000S" type="static" mediaPresentationDuration="PT0H0M15.000S" profiles="urn:mpeg:dash:profile:isoff-on-demand:2011">
  <ProgramInformation moreInformationURL="http://.../info">
    <Title>Ad Pod 1</Title>
  </ProgramInformation>
  <Period duration="PT0H0M5.000S" id="ad-pod-1-period-1">
    ...
  </Period>
  <Period duration="PT0H0M5.000S" id="ad-pod-1-period-2">
    ...
  </Period>
  <Period duration="PT0H0M5.000S" id="ad-pod-1-period-3">
    ...
  </Period>
  ...
</MPD>

Example stitched MPD

Serve this as your response to the initial stream manifest request.

<?xml version="1.0" encoding="UTF-8"?>
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" minBufferTime="PT1.500000S" type="static" mediaPresentationDuration="PT0H10M15.000S" profiles="urn:mpeg:dash:profile:isoff-on-demand:2011">
  <ProgramInformation moreInformationURL="http://.../info">
    <Title>Example Stream</Title>
  </ProgramInformation>
  <Period duration="PT0H0M15.000S" id="content-period-1">
    ...
  </Period>
  <Period duration="PT0H0M5.000S" id="ad-pod-1-period-1">
    ...
  </Period>
  <Period duration="PT0H0M5.000S" id="ad-pod-1-period-2">
    ...
  </Period>
  <Period duration="PT0H0M5.000S" id="ad-pod-1-period-3">
    ...
  </Period>
  <Period duration="PT0H0M15.000S" id="content-period-2">
    ...
  </Period>
  <Period duration="PT0H0M15.000S" id="content-period-3">
    ...
  </Period>
  ...
</MPD>

Additional resources