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 media playlist.
This API is intended for advanced publishers and video technology partners. Using this API at scale requires design and implementation of a sophisticated media serving workflow which is outside the scope of this documentation.
This guide will focus on proper implementation of a Pod Serving manifest manipulation server.
Implementation
Setting up encoding profiles and live events in Ad Manager
Pod Serving requires LiveStreamEvents to be configured in Ad Manager 360, either using the Ad Manager API (recommended) or the UI. Additionally, information about the encoding profiles used for the content must be configured via the API or UI.
Encoding Profile Setup
Encoding profiles must be created in each Ad Manager network, via the DAIEncodingProfile API or UI.
An Encoding Profile consists of information describing the encoding of a single content variant. It may contain only audio settings, only video settings, or both audio and video settings. It allows us to determine which particular ad transcode should be returned for a given ad segment request.
Encoding Profile | |
---|---|
name
|
An identifier for the Encoding Profile. Used to build the ad segment URLs for this variant. This should be unique for a network. |
id
|
Unique ID of the profile. This value is read-only and is assigned by Google. |
type
|
Either media, iFrame, or subtitles. |
containerType
|
Container type. Either TS, FMP4, or HLS Packed Audio. |
Video Settings (only present if media contains video, or if an iFrame type) | |
codec
|
Required. The RFC6381 codec string. |
bitrate
|
Required. |
framesPerSecond
|
Required. Double representing FPS of the video. Example: 29.97. |
resolution
|
Required. The width x height of the video. Example: 3840x2160. |
Audio Settings (only present if media contains audio) | |
codec
|
Required. The RFC6381 codec string. |
bitrate
|
Required if the Encoding Profile only contains audio. Optional if it also contains Video. |
audioChannels
|
Required. Integer representing number of audio channels (including low frequency channels). Example: 6 |
audioSampleRate
|
Required. Audio sample rate in hertz. Example: 68000 |
Live Stream Event Setup
Live Stream Events must be configured via the LiveStreamEventService API or UI.
Required Fields | |
---|---|
name
|
Name of the live stream |
customAssetKey
|
A custom identifier to be used for this Event. Must be unique across all Events for the network. |
status
|
At creation, events are created in the LiveStreamEventStatus.PAUSED state.
Note: Set to |
adTags
|
Master ad tag URL generated by the Ad Manager trafficking workflow |
daiEncodingProfileIds
|
A list of the encoding profile ids enabled for this Event. |
dynamicAdInsertionType
|
Set to POD_SERVING_REDIRECT
|
startDateTime
|
The start date and time of this LiveStreamEvent. |
endDateTime
|
The scheduled end date and time of this LiveStreamEvent. This attribute is required if unlimitedEndDateTime is false and ignored if unlimitedEndDateTime is true. |
segmentUrlAuthenticationKeyIds
|
List of DAI Authentication keys used to sign ad segment URL requests |
Recommended optional fields | |
---|---|
prefetchEnabled
|
Indicates whether the option to prefetch ad requests is enabled. |
prefetchSettings
|
The information needed to prefetch ad requests for an ad break. |
slateCreativeId
|
ID corresponding to the slate for this live event. If not set, the network default value will be used. |
Fields not applicable | |
---|---|
adBreakFillType
|
Only slate is currently available as adBreakFillType. |
contentUrls
|
No content ingest occurs with Pod serving |
sourceContentConfigurationIds
|
No content ingest occurs with Pod serving |
hlsSettings
|
|
streamingFormat
|
This can be specified on stream create. |
maxFillerDuration
|
|
whitelistedIpsEnabled
|
No content ingest occurs with Pod serving |
enableRelativePlaylistDelivery
|
Pod serving does not serve the manifest |
enableForceCloseAdBreaks
|
Pod serving does not serve the manifest |
segmentDropping
|
Pod serving does not serve the manifest |
Dai Authentication Key Setup
In order to sign ad segment URLs, DAI Authentication Keys must be created and added to the LiveStreamEvent using the following steps:
- Call DaiAuthenticationKeyService API and generate key(s) of type HMAC
- Add keys to the
segmentUrlAuthenticationKeyIds
field of LiveStreamEventService API.
Once you have properly configured your events and encoder profiles, you should have the following information, which will be needed by the stitching server:
- network_code - The Ad Manager 360 network code for this network.
- custom_asset_key - The custom live stream asset key assigned to your event.
- profile_names - The names of the encoding profiles used by this event.
- HMAC_authentication_key - The secret authentication key associated with your live stream event.
Handling stream manifest requests
When your server receives a stream manifest request from the client, you must store the stream_id provided in the URL, so that it can be incorporated into each segment request. Then, you must begin streaming the manifest to the client, ready to modify each defined ad pod, before sending to the client.
Identifying ad breaks
Identifying ad breaks in your unprocessed manifest will vary depending on your encoder. One common method is to wrap the ad break in #EXT-X-CUE-OUT/#EXT-CUE-IN tags that will denote the duration of the ad pod.
Regardless of how ad breaks are delineated in your stream, as you process these breaks, you will need to keep track of a few crucial values:
- ad_pod_index - This is the index (starting at 1) of the ad pod being processed, within the stream, starting from the beginning of the stream. This ad pod index must match globally for all stream clients and must increment by 1 for each new ad break.
- ad_pod_duration - The total duration of this ad pod, in milliseconds. In the case of the example below, the duration would be stored as 18000.
- cust_params - Custom parameters for per break targeting, in the format described in this Ad Manager Help Center article.
Sample Manifest (original)
#EXTM3U
#EXT-X-VERSION:6
#EXT-X-TARGETDURATION:6
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:5.005,
contentorigin.com/1.ts
#EXTINF:5.005,
contentorigin.com/2.ts
#EXT-X-CUE-OUT:DURATION=18
#EXTINF:5.005,
contentorigin.com/3.ts
#EXTINF:5.005,
contentorigin.com/4.ts
#EXTINF:5.005,
contentorigin.com/5.ts
#EXTINF:3.000,
contentorigin.com/6.ts
#EXT-X-CUE-IN
#EXTINF:5.005,
contentorigin.com/7.mp4
#EXTINF:5.005,
contentorigin.com/8.mp4
Insert discontinuities between ads and content
To separate Google-hosted ad breaks from your content segments, you must insert #EXT-X-DISCONTINUITY
tags at the start and end of each ad break. If these discontinuity tags do not appear in the final manifest, playback will fail.
The inserted ad segment URIs will be unencrypted. If your content is encrypted, you will also need to remove encryption by specifying #EXT-X-KEY:METHOD=NONE
prior to the first ad segment of each ad break and then re-add it after the ad break.
Generate HMAC Token for Pod
Each segment request within an ad pod must be signed with an HMAC-SHA256
token. This token is generated from the preceding stream variables, and an
expiration timestamp in seconds. You can omit any variable that doesn't
have a value, or set it to an empty string. You need to order the variables
alphabetically, then add the hmac
variable to the end.
The HMAC token is generated from a message string in the following format:
custom_asset_key={custom_asset_key}~cust_params={cust_params}~exp={expiration}~network_code={network_code}~pd={pod_duration}~pod_id={ad_pod_index}
The HMAC token can be generated once per adpod and shared across users for all segments in that ad pod to avoid needing to compute each time.
The secret key is an HMAC authentication key configured in Ad Manager. The key must then be added to the LiveStreamEvent as an Ad Segment Authentication Key.
Example 1
Here's an example using variables that don't have values.
Message to encode (cust_params and scte35 are set to empty strings):
custom_asset_key=iYdOkYZdQ1KFULXSN0Gi7g~cust_params=~exp=1489680000~network_code=6062~pd=180000~pod_id=5~scte35=
Secret key:
A7490591290583E4B93189DEE7E287C299FC686872ABC7ADC9F9F536443505F
HMAC signature output:
86d7e5f8c96fe4c83141d764df376ae14a0e2066f2e6b2ccfb9e1e2d3c869a88
Once generated, the output should be appended to the message to encode to obtain the final HMAC token:
custom_asset_key=iYdOkYZdQ1KFULXSN0Gi7g~cust_params=~exp=1489680000~network_code=6062~pd=180000~pod_id=5~scte35=~hmac=86d7e5f8c96fe4c83141d764df376ae14a0e2066f2e6b2ccfb9e1e2d3c869a88
The token should then be URL-encoded to be used as a query parameter:
custom_asset_key%3DiYdOkYZdQ1KFULXSN0Gi7g~cust_params%3D~exp%3D1489680000~network_code%3D6062~pd%3D180000~pod_id%3D5~scte35%3D~hmac%3D86d7e5f8c96fe4c83141d764df376ae14a0e2066f2e6b2ccfb9e1e2d3c869a88
Example 2
Here’s an example where some optional variables are omitted entirely.
Message to encode:
custom_asset_key=iYdOkYZdQ1KFULXSN0Gi7g3~exp=1489680000~network_code=6062~pd=180000~pod_id=5
Secret key:
A7490591290583E4B93189DEE7E287C299FC686872ABC7ADC9F9F536443505F
HMAC signature output:
6a8c44c72e4718ff63ad2284edf2a8b9e319600b430349d31195c99b505858c9
Once generated, the output should be appended to the message to encode to obtain the final HMAC token:
custom_asset_key%3DiYdOkYZdQ1KFULXSN0Gi7g~exp%3D1489680000~network_code%3D6062~pd%3D180000~pod_id%3D5~hmac%3D6a8c44c72e4718ff63ad2284edf2a8b9e319600b430349d31195c99b505858c9
The token should then be URL-encoded to be used as a query parameter:
custom_asset_key%3DiYdOkYZdQ1KFULXSN0Gi7g~cust_params%3D~exp%3D1489680000~network_code%3D6062~pd%3D180000~pod_id%3D5~scte35%3D~hmac%3D86d7e5f8c96fe4c83141d764df376ae14a0e2066f2e6b2ccfb9e1e2d3c869a88
Processing ad pod segments
For each segment within an ad pod, you will need to track a few additional values:
- segment_number - Segment index within the ad pod, starting with zero
- segment_duration - Duration of the current segment in milliseconds. This value should be the same for all segments except the last one in the pod.
- segment_offset - Segment offset, calculated by adding the previous segment's duration to its segment offset in milliseconds.
- last - Boolean value, identifying the last segment in an ad pod. Defaults to false.
Building ad segment urls
Replace each segment within the ad break with a url of the format:
/linear/pods/v1/seg/network/{network_code}/custom_asset/{custom_asset_key}/pod/{pod_id}/profile/{profile_name}/{segment_number}.(ts|mp4|vtt|aac|ac3|eac3)
Path Parameters | |
---|---|
network_code
|
The Ad Manager 360 network code for this network. |
custom_asset_key
|
The custom live stream asset key, specified in LiveStreamEventService API or on the Live Stream page in the Google Ad Manager 360 UI. |
pod_id
|
Identifier for the adbreak, should be an integer starting at one and increasing by one for each ad break.
This value must be the same across all users viewing the current event. |
profile_name
|
Identifier for the profile being requested, |
segment_number
|
The index of this segment within the current ad pod, starting at zero. |
Query Parameters | ||
---|---|---|
stream_id
|
Required for cookieless request.
|
The user’s stream_id param returned from the Stream Create request.
|
sd
|
Required | segment_duration |
so
|
Optional | segment_offset
If |
pd
|
Required, except for events with durationless ad breaks enabled. | The duration (in milliseconds) of the ad break. Referred to above as ad_pod_duration. |
cust_params
|
Optional | custom_params (as described above) |
scte35
|
Optional | Base64-encoded SCTE-35 signal. It’s the client's responsibility to ensure that the signal is correct. If incorrect, a message is sent to the X-Ad-Manager-Dai-Warning HTTP header in the response, and the signal is still propagated to create an ad break. See the supported ad markers for more information on how DAI uses the SCTE-35 signal. | auth-token
|
Required | The HMAC token for this ad pod |
last
|
Optional | Boolean indicating this is the last segment in the adbreak or not. Defaults to false. |
Sample Manifest (after segment replacement)
#EXTM3U
#EXT-X-VERSION:6
#EXT-X-TARGETDURATION:6
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:5.005,
contentorigin.com/1.ts
#EXTINF:5.005,
contentorigin.com/2.ts
#EXT-X-DISCONTINUITY
#EXTINF:5.005,
https://dai.google.com/linear/pods/v1/seg/network/6062/custom_asset/iYdOkYZdQ1KFULXSN0Gi7g/pod/1/profile/devrel4628000/0.ts?sd=5005&so=0&pd=18015&auth-token=custom_asset_key%3DiYdOkYZdQ1KFULXSN0Gi7g~cust_params%3D~exp%3D1489680000~network_code%3D6062~pd%3D180000~pod_id%3D5~hmac%3D44bf78223c240cbc5bae3cdfd794bfc6971b6583cd296f44ef3a46944605cf9a&stream_id=fe6c9136-09a4-4ff6-862e-daee1dea0e1b:MRN2
#EXTINF:5.005,
https://dai.google.com/linear/pods/v1/seg/network/6062/custom_asset/iYdOkYZdQ1KFULXSN0Gi7g/pod/1/profile/devrel4628000/1.ts?sd=5005&so=5005&pd=18015&auth-token=custom_asset_key%3DiYdOkYZdQ1KFULXSN0Gi7g~cust_params%3D~exp%3D1489680000~network_code%3D6062~pd%3D180000~pod_id%3D5~hmac%3D44bf78223c240cbc5bae3cdfd794bfc6971b6583cd296f44ef3a46944605cf9a&stream_id=fe6c9136-09a4-4ff6-862e-daee1dea0e1b:MRN2
#EXTINF:5.005,
https://dai.google.com/linear/pods/v1/seg/network/6062/custom_asset/iYdOkYZdQ1KFULXSN0Gi7g/pod/1/profile/devrel4628000/2.ts?sd=5005&so=10010&pd=18015&auth-token=custom_asset_key%3DiYdOkYZdQ1KFULXSN0Gi7g~cust_params%3D~exp%3D1489680000~network_code%3D6062~pd%3D180000~pod_id%3D5~hmac%3D44bf78223c240cbc5bae3cdfd794bfc6971b6583cd296f44ef3a46944605cf9a&stream_id=fe6c9136-09a4-4ff6-862e-daee1dea0e1b:MRN2
#EXTINF:3.000,
https://dai.google.com/linear/pods/v1/seg/network/6062/custom_asset/iYdOkYZdQ1KFULXSN0Gi7g/pod/1/profile/devrel4628000/3.ts?sd=3000&so=15015&pd=18015&auth-token=custom_asset_key%3DiYdOkYZdQ1KFULXSN0Gi7g~cust_params%3D~exp%3D1489680000~network_code%3D6062~pd%3D180000~pod_id%3D5~hmac%3D44bf78223c240cbc5bae3cdfd794bfc6971b6583cd296f44ef3a46944605cf9a&stream_id=fe6c9136-09a4-4ff6-862e-daee1dea0e1b:MRN2&last=true
#EXT-X-DISCONTINUITY
#EXTINF:5.005,
contentorigin.com/7.mp4
#EXTINF:5.005,
contentorigin.com/8.mp4
Congratulations! You are now properly serving a live stream with ad segments provided by the DAI pod serving API.
Best practices
- Please note that while Ad pods are indexed starting at one, the ad segments contained within should be indexed starting at zero and be the same for all users.
- An
#EXT-X-DISCONTINUITY
must precede the first ad segment of an ad break and follow the last segment of an ad break. - The inserted ad segment URIs will be unencrypted. If your content is encrypted, you will also need to remove encryption by specifying
#EXT-X-KEY:METHOD=NONE
prior to the first ad segment of each ad break and then re-add it after the ad break. - Each ad segment url within a given variant should be the same duration (up to 6 seconds). An exception to this rule is the last ad segment in the ad pod, which can be of shorter duration. Ad segments in different variants do not need to be the same length.
- Playlists should follow the HLS spec. Media sequence numbers, discontinuity sequence numbers, and program date time must be updated appropriately.
- Ad pod indices should be consistent across all users watching the stream. All user’s watching the same ad break should have the same pod ID.
Additional Resources
- Pod serving playback with the interactive media ads SDK:
- Pod serving playback with the DAI API
- Early Ad Break Notification with the EABN API
- Working with content encoding profiles via the DaiEncodingProfileService
- Working with LiveStreamEvents via the LiveStreamEventService