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:
|
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: |
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: |
Audio settings | ||
codec |
Required |
The RFC6381 codec string.
Example: |
bitrate |
Required |
An integer representing the max audio bitrate of this profile in bytes per
second.
Example: |
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: |
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: |
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
The value |
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
- Pod serving playback with the IMA SDK:
- Pod serving playback with the DAI API