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}
stream_id
content_id
format
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 and ad tag. 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
network_code
stream_id
JSON body
encoding_profiles
Required
To make playback as seamless as possible, this should match the set of encoding profiles used in your content stream.
ad_tag
Required
cuepoints
Optional
Required only for VMAP responses that contain mid-rolls using positional time offsets. This is uncommon.
content_duration_seconds
Optional
Required only for VMAP responses that contain mid-rolls using percentage time offsets. This is uncommon.
manifest_type
Optional
hls
or dash
. The default value is hls
.dai_options
Optional
Encoding profile
profile_name
Required
type
Required
media
, iframe
, subtitles
.container_type
Required
mpeg2ts
, fmp4cmaf
, hls_packed_audio
video_settings
Optional
audio_settings
Optional
Video settings
codec
Required
Example:
avc1.4d000c
bitrate
Required
frames_per_second
Required
resolution
Required
Example:
{"width": 640, "height": 320}
Audio settings
codec
Required
Example:
mp4a.40.5
bitrate
Required
Example:
300000
channels
Required
sample_rate
Required
Example:
4800
Subtitle settings
format
Required
webvtt
or ttml
.language
Optional
Example:
en-us
DAI options
dash_profile
Optional
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
ad_pods
response and stops
processing.sam_id
Optional
Response
valid_for
dhms
(days, hours, minutes, seconds) format.valid_until
yyyy-MM-dd'T'hh:mm:ss.sssssssss[+|-]hh:mm
format.ad_pods
manifest_uris
mpd_uri
type
pre
, mid
, or post
.start
duration
midroll_index
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""
},
"
t
ype
": "
pos
t
",
"
dura
t
io
n
": 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