Skip to content

Integration Flow ​

A tvOS integration is split between Infillion and the publisher. Infillion provides the VAST tag and renders the interactive ad. The publisher's app finds the Infillion ad in its ad break, hands it to TruexAdRenderer, and moves the playback timeline forward when the renderer is done.

  1. Infillion provides a test VAST tag

    Your Infillion contact provides a test VAST tag that fits your ad setup. The generic tag is the preferred option. The companion tag is used when it works better with your ad stack. Both formats are described in VAST Tag Types.

  2. The publisher traffics the tag

    Your ad ops team schedules the tag in your ad server. A TrueX tag must be the first ad in the pod. An IDVx tag (SDK 4.x) can be placed in any slot.

  3. The publisher integrates the SDK

    Your app resolves adParameters from the ad, starts the renderer when the ad plays, and handles the terminal events.

Resolving adParameters ​

The renderer is initialized with an adParameters dictionary. Where that JSON comes from depends on the VAST tag format and how your app receives the ad:

Tag formatWhere adParameters is in the VASTGoogle IMA DAICustom VAST parsing
Generic<Linear><AdParameters> as a JSON stringad.traffickingParametersText content of <AdParameters>
Companion<Companion apiFramework="truex"><StaticResource creativeType="application/json"> as a base64 JSON data: URLUse the generic tag with Google IMAText content of <StaticResource>, base64-decoded after the comma

First, identify the ad by its <AdSystem> value (ad.adSystem in Google IMA), compared case-insensitively. trueX is a TrueX ad; any other value is a regular linear ad. A TrueX ad that is not the first ad in the pod should play as a regular linear ad.

Upcoming: SDK 4.x

With SDK 4.x, IDVx is an Infillion ad too, and it is allowed in any pod position. With SDK 3.x, play an IDVx ad as a regular linear ad.

To support both formats, resolve the payload in this order:

  1. Look for a companion with apiFramework="truex" and decode its JSON.
  2. If there is none, parse the <AdParameters> JSON.
  3. If neither one gives a JSON object, do not start the renderer. Treat the ad as a normal linear ad and continue the pod.

The parsing code:

swift
/// Decodes a `data:application/json;base64,...` URL. Any other resource is read as plain JSON.
func companionAdParameters(_ resource: String) -> [String: Any]? {
    let trimmed = resource.trimmingCharacters(in: .whitespacesAndNewlines)
    guard trimmed.lowercased().hasPrefix("data:") else {
        return jsonObject(trimmed)
    }
    // The ad server wraps the base64 payload across lines, so whitespace is removed before decoding.
    let compact = trimmed.filter { !$0.isWhitespace }
    guard let comma = compact.firstIndex(of: ","),
        let decoded = Data(base64Encoded: String(compact[compact.index(after: comma)...]))
    else {
        return nil
    }
    return (try? JSONSerialization.jsonObject(with: decoded)) as? [String: Any]
}

/// Parses `<AdParameters>` (or IMA's `traffickingParameters`). Returns nil unless it is a JSON object.
func jsonObject(_ text: String) -> [String: Any]? {
    guard let json = text.trimmingCharacters(in: .whitespacesAndNewlines).data(using: .utf8) else {
        return nil
    }
    return (try? JSONSerialization.jsonObject(with: json)) as? [String: Any]
}
objective-c
// Parses `<AdParameters>` (or IMA's `traffickingParameters`). Returns nil unless it is a JSON object.
static NSDictionary *JsonObject(NSString *text) {
    NSData *json = [[text stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceAndNewlineCharacterSet]
        dataUsingEncoding:NSUTF8StringEncoding];
    if (json.length == 0) {
        return nil;
    }
    id object = [NSJSONSerialization JSONObjectWithData:json options:0 error:nil];
    return [object isKindOfClass:[NSDictionary class]] ? object : nil;
}

// Decodes a `data:application/json;base64,...` URL. Any other resource is read as plain JSON.
static NSDictionary *CompanionAdParameters(NSString *resource) {
    NSString *trimmed = [resource stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceAndNewlineCharacterSet];
    if (![trimmed.lowercaseString hasPrefix:@"data:"]) {
        return JsonObject(trimmed);
    }
    // The ad server wraps the base64 payload across lines, so whitespace is removed before decoding.
    NSString *compact = [[trimmed componentsSeparatedByCharactersInSet:NSCharacterSet.whitespaceAndNewlineCharacterSet]
        componentsJoinedByString:@""];
    NSRange comma = [compact rangeOfString:@","];
    if (comma.location == NSNotFound) {
        return nil;
    }
    NSData *decoded = [[NSData alloc] initWithBase64EncodedString:[compact substringFromIndex:NSMaxRange(comma)]
                                                          options:0];
    if (!decoded) {
        return nil;
    }
    id object = [NSJSONSerialization JSONObjectWithData:decoded options:0 error:nil];
    return [object isKindOfClass:[NSDictionary class]] ? object : nil;
}

With Google IMA, pass ad.traffickingParameters to jsonObject (Objective-C: JsonObject).

Starting the Renderer ​

When the Infillion ad starts playing:

  1. Pause the content player and the ad playback.
  2. Prepare to move past the placeholder ad video:
    • SSAI (for example Google IMA DAI): the placeholder is stitched into the stream, so remember where it ends (the current stream time plus ad.duration) to seek there later.
    • Custom ad layers: seek the placeholder to just before its end, so it finishes right away when the ad playback resumes.
  3. Create a TruexAdRenderer with the adParameters dictionary and your delegate. If the renderer can't be created, there will be no delegate events: continue the pod.
  4. Call start with the full-screen view controller that shows your player.
  5. Wait for a terminal event. While you wait, keep track of whether onAdFreePod has been called.

The calls are described in the SDK Integration page of your SDK version: 3.x or 4.x.

You can also create the renderer a few seconds before the ad break starts, so it has more time for its ad request, and call start when the player reaches the Infillion ad.

Handling Terminal Events ​

A terminal event means the renderer is done, and your app must move the playback timeline forward. What "forward" means depends on whether the user earned the ad-free pod reward and on how your ads are inserted:

Renderer resultWhat to doGoogle IMA DAI (SSAI)Custom ad layer
onAdCompleted after onAdFreePodSkip all remaining ads in the pod and continue the contentSeek past the end of the current ad break's cue pointEnd the pod and restore the content
onAdCompleted without onAdFreePodSkip the Infillion placeholder ad and play the next ad in the podSeek to just before the placeholder's end, so the next stitched ad playsPlay the next ad in the pod
onAdError or onNoAdsAvailableSame as onAdCompleted without onAdFreePodSameSame
onUserCancelStreamThe user closed the Infillion ad because they want to leave - exit the playback screenClose the playerClose the player

onAdCompleted without onAdFreePod covers a TrueX user who opted out or let the choice card time out. With SDK 4.x it is also the normal outcome of every IDVx ad, because IDVx never calls onAdFreePod.

IMPORTANT

Do not seek or resume playback when onAdFreePod is called. Remember that it was called, and act only on the terminal event that follows.

After any terminal event, call stop and drop all references to the renderer instance. The full list of terminal events is on the Events page of your SDK version: 3.x or 4.x.

Integration documentation for Infillion Ads