This guide covers developing a client application to load an HLS or DASH
livestream with Pod serving API and your manifest manipulator.

## Prerequisites

Before continuing, you must have the following:

- A custom asset key for a livestream event configured with the `Pod serving
  redirect` DAI type. To get this key, follow:

  - [Set up a livestream for
    DAI](https://support.google.com/admanager/answer/7294289).

  - Use a SOAP API client library to call the
    [`LiveStreamEventService.createLiveStreamEvents`](https://developers.google.com/ad-manager/api/reference/latest/LiveStreamEventService#createlivestreamevents)
    method with a `LiveStreamEvent` object and the `dynamicAdInsertionType`
    property set to the [`POD_SERVING_REDIRECT`](https://developers.google.com/ad-manager/api/reference/latest/LiveStreamEventService.DynamicAdInsertionType)
    enum value. For all client libraries, see [Client libraries and example
    code](https://developers.google.com/ad-manager/api/clients).

- Determine if the Interactive Media Ads (IMA) SDK is available for your
  platform. We recommend using the IMA SDK to enhance revenue. For details,
  see [Set up the IMA SDk for
  DAI](https://developers.google.com/ad-manager/dynamic-ad-insertion/sdk/html5/get-started?service=pod).

## Make a stream request

When your user selects a stream, do the following:

1. Make a `POST` request to the livestream service method. For details, see
   [Method:
   stream](https://developers.google.com/ad-manager/dynamic-ad-insertion/api/pod-serving/reference/live#method_stream).

2. Pass ad targeting parameters in `application/x-www-form-urlencoded` or
   `application/json` formats. This request registers a stream session with
   Google DAI.

   The following example makes a stream request:

   ### Form encoding

       const url = `https://dai.google.com/ssai/pods/api/v1/` +
             `network/NETWORK_CODE/custom_asset/CUSTOM_ASSET_KEY/stream`;

       const params = new URLSearchParams({
               cust_params: 'section=sports&page=golf,tennis'
       }).toString();

       const response = await fetch(url, {
               method: 'POST',
               headers: {
                 'Content-Type': 'application/x-www-form-urlencoded'
               },
               body: params
       });

       console.log(await response.json());

   ### JSON encoding

       const url = `https://dai.google.com/ssai/pods/api/v1/` +
             `network/NETWORK_CODE/custom_asset/CUSTOM_ASSET_KEY/stream`;

       const response = await fetch(url, {
               method: 'POST',
               headers: {
                 'Content-Type': 'application/json'
               },
               body: JSON.stringify({
                 cust_params: {
                   section: 'sports',
                   page: 'golf,tennis'
                 }
               })
       });

       console.log(await response.json());

   If successful, you see output similar to the following:

       {
       "stream_id": "c4a5dad5-aaa8-4550-8acb-7cda3cdb21bb:DLS",
       "media_verification_url": "https://dai.google.com/view/.../event/c14aZDWtQg-ZwQaEGl6bYA/media/",
       "metadata_url": "https://dai.google.com/linear/pods/hls/.../metadata",
       "session_update_url": "https://dai.google.com/linear/.../session",
       "polling_frequency": 10
       }

3. In the JSON response, locate the stream session ID and store other data for
   subsequent steps.

   > [!IMPORTANT]
   > **Important:** For your app to function, use the URLs provided in the response directly. Don't construct the URLs, as the paths might change.

## Poll ad metadata

To poll ad metadata, do the following:

1. Read the `metadata_url` value from the stream registration response.

2. Make an initial `GET` request to the `metadata_url` endpoint.

   - Omit the `delta_token` query parameter. This process lets the server return the full metadata for the stream's Digital Video Recorder (DVR) window. The DVR window contains the timeframe of the broadcast available for a viewer to rewind and play. The response includes a `next_delta_token` field.
3. To optimize bandwidth, store the `next_delta_token` value from the most
   recent response.

4. On your next request, send that value as the `delta_token` query parameter.
   The server returns only the metadata that changed since that token was
   generated. Always send the latest token you received.
   Don't attempt to parse, modify, or construct
   the token. For details, see
   [Method: metadata](https://developers.google.com/ad-manager/dynamic-ad-insertion/api/pod-serving/reference/live#method_metadata).

   The following example fetches ad metadata:

       // Initial request (returns full metadata and next_delta_token)
       let response = await fetch(metadata_url);
       let metadata = await response.json();
       let deltaToken = metadata.next_delta_token;

       // Subsequent request (returns only changes since deltaToken)
       if (deltaToken) {
         const url = new URL(metadata_url);
         url.searchParams.append('delta_token', deltaToken);
         response = await fetch(url.toString());
         const deltaMetadata = await response.json();
         // Merge deltaMetadata into your local cache
         mergeMetadata(metadata, deltaMetadata);
         deltaToken = deltaMetadata.next_delta_token;
       }

   If successful, you receive the
   [PodMetadata](https://developers.google.com/ad-manager/dynamic-ad-insertion/api/pod-serving/reference/live#podmetadata)
   response. If you provide the `delta_token` parameter, the response contains
   only the ads, ad breaks, and tags that the server added or updated since
   the server generated the token. The response also contains a new `next_delta_token`
   value. If any ad breaks are outdated, the response also includes an
   `obsolete_ad_break_ids` list of the ad breaks to remove from your cache.

       {
         "next_delta_token": "eyJyYW5nZXMiOlt7InMiOjEsImUiOjN9XX0",
         "obsolete_ad_break_ids": ["0003069407"],
         "tags":{
           "google_1022389921":{
             "ad":"0003069408_ad1",
             "ad_break_id":"0003069408",
             "type":"start"
           },
           ...
         },
         "ads":{
           "0003069408_ad1":{
             "ad_break_id":"0003069408",
             "position":1,
             "duration":10.01,
             "title":"External - Pod Midroll 1",
             "clickthrough_url":"https://.../",
             ...
           },
           ...
         },
         "ad_breaks":{
           "0003069408":{
             "type":"mid",
             "duration":30,
             "ads":3
           },
           ...
         }
       }

5. Save the `tags` object and merge updates into your local cache. If the
   `obsolete_ad_break_ids` parameter is present, remove those ad breaks and
   associated ads and tags from your cache.

6. Set a timer using the `polling_frequency` value to regularly request
   metadata. In each poll, send the `next_delta_token` value returned in the
   most recent metadata response as the `delta_token` query parameter.

## Load the stream into your video player

After you have the session ID from the registration response, pass the ID to
your manifest manipulator, or construct a manifest URL to load the stream into a
video player.

To pass the session ID, see your manifest manipulator documentation. If you
develop a manifest manipulator, see [Manifest manipulator for
livestream](https://developers.google.com/ad-manager/dynamic-ad-insertion/api/pod-serving/live/manipulator).

The following example assembles a manifest URL:

    https://<your_manifest_manipulator_url>/manifest.m3u8?DAI_stream_ID=SESSION_ID&network_code=NETWORK_CODE&DAI_custom_asset_key=CUSTOM_ASSET_KEY"

When your player is ready, begin playback.

## Listen for ad events

Check your stream's container format for the timed metadata:

- HLS streams with Transport Stream (TS) containers use timed ID3 tags to
  carry timed metadata. For details, see [About the Common Media
  Application Format with HTTP Live Streaming
  (HLS)](https://developer.apple.com/documentation/http-live-streaming/about-the-common-media-application-format-with-http-live-streaming-hls).

- DASH streams use `EventStream` elements to specify events in
  the manifest.

- DASH streams use `InbandEventStream` elements when the segments contain
  Event Message (`emsg`) boxes for payload data, including ID3 tags. For
  details, see [InbandEventStream](https://dashif.org/identifiers/event_schemes/).

- CMAF streams, including DASH and HLS, use `emsg` boxes containing ID3 tags.

To retrieve ID3 tags from your stream, refer to your video player's guide. For
details, see [Handle timed metadata
guide](https://developers.google.com/ad-manager/dynamic-ad-insertion/sdk/html5/timed-metadata)

To retrieve the ad event ID from ID3 tags, do the following:

1. Filter the events by `scheme_id_uri` with `urn:google:dai:2018` or `https://aomedia.org/emsg/ID3`.
2. Extract the byte array from the `message_data` field.

   The following example decodes the `emsg` data into JSON:

       {
         "scheme_id_uri": "https://developer.apple.com/streaming/emsg-id3",
         "presentation_time": 27554,
         "timescale": 1000,
         "message_data": "ID3TXXXgoogle_1022389921",
         ...
       }

3. Filter the ID3 tags with the format `TXXXgoogle_{ad_event_ID}`:

       TXXXgoogle_1022389921

## Show ad event data

To find the
[`TagSegment`](https://developers.google.com/ad-manager/dynamic-ad-insertion/api/pod-serving/reference/live#tagsegment)
object, do the following:

1. Retrieve the ad metadata `tags` object from [Poll ad
   metadata](https://developers.google.com/ad-manager/dynamic-ad-insertion/api/pod-serving/live/client#poll_ad_metadata). The `tags` object is an array of `TagSegment`
   objects.

2. Use the full ad event ID to find a `TagSegment` object with the type
   `progress`.

3. Use the first 17 characters of the ad event ID to find a `TagSegment` object
   of other types.

   Because your client app polls ad metadata periodically, a delay might occur
   between when your video player encounters an ID3 tag in the stream and when
   the associated metadata is available. If your client app doesn't find an ID3
   tag in the stored tags, keep the tag in a queue and re-process the tag after
   the next metadata poll. Keep the tag in the queue until processing finishes.
4. After you have the `TagSegment`, use the `ad_break_id` property as the key
   to find the
   [`AdBreak`](https://developers.google.com/ad-manager/dynamic-ad-insertion/api/pod-serving/reference/live#adbreak)
   object in the ad metadata `ad_breaks` object.

   The following example finds an `AdBreak` object:

       {
         "type":"mid",
         "duration":15,
         "ads":1
       }

5. Use the `TagSegment` and `AdBreak` data to show information about the ad
   position in the ad break. For example, `Ad 1 of 3`.

## Send media verification pings

For every ad event, except the `progress` type, send a media verification ping.
Google DAI discards `progress` events, and sending these events frequently might
impact your app performance.

To generate the complete media verification URL of an ad event, do the
following:

1. From the stream response, append the full ad event ID to the
   `media_verification_url` value.

2. Make a `GET` request with the complete URL:

       // media_verification_url: "https://dai.google.com/view/.../event/c14aZDWtQg-ZwQaEGl6bYA/media/"
       const completeUrl = `${media_verification_url}google_1022389921`;

       const response = await fetch(completeUrl);

   If successful, you receive a code status `202` response. Otherwise, you
   receive a `404` error code.

You can use the Stream Activity Monitor (SAM) to inspect a historical log of all
ad events. For details, see [monitor and troubleshoot a
livestream](https://support.google.com/admanager/answer/13205146)