--- url: /ads/publisher/overview/what-are-infillion-ads.md --- # What are Infillion Ads? Infillion is a leading advertising technology company that offers innovative ad solutions including [TrueX][truex_product] and [Interactive Video][interactive_video_product] (Interactive Digital Video Experience). ## TrueX - Opt-in Engagement Advertising [TrueX][truex_product] is Infillion’s flagship ad product. Fully interactive and always opt-in, TrueX allows consumers to choose to skip the Publisher’s regular ad break by interacting for 30 seconds with a TrueX engagement ad. Infillion pays the Publisher the same price or more for that skipped ad pod. Infillion works with a diverse range of video streaming Publishers on many different environments, devices and platforms, while driving best in class results for advertisers. TrueX provides a better experience for the viewer, reduces ad load for Publishers, and produces 5x the brand impact for Advertisers over standard video spots. ## **Interactive Video** [Interactive Video][interactive_video_product] from Infillion delivers a new industry standard in video engagement across all devices and platforms. The Infillion IDVx product goes beyond hovers and inadvertent clicks, to deliver real interaction, by real humans, in premium environments. Built on the same core interactive foundation as TrueX, IDVx removes the opt-in, interaction, and time spent requirements, in order to capitalize on the scale of a Publisher’s inventory with beautiful, engaging, and highly IDVx ads. Infillion pays Publishers a set CPM for the IDVx ads the Publisher renders in their ad pods. [truex_product]: https://infillion.com/products/infillion-truex/ [interactive_video_product]: https://infillion.com/products/infillion-interactive-video/ --- --- url: /ads/publisher/overview/vast-tags.md --- ## VAST Tag Types Infillion uses two types of VAST tags for both TrueX and Interactive Video products. The tags differ in how the `adParameters` JSON payload is embedded in the VAST XML. ### "Generic" Tag The `adParameters` are embedded directly as a JSON string within the `` node: **TrueX "Generic" Tag:** ``` https://get.truex.com/{placement_hash}/vast/generic?{ad_request_parameters} ``` **Interactive Video "Generic" Tag:** ``` https://get.truex.com/{placement_hash}/vast/idvx/generic?{ad_request_parameters} ``` **VAST Structure:** ```xml trueX 00:00:30 ``` ### "Companion" Tag The `adParameters` are embedded in a companion ad's `` node as base64-encoded json string: **TrueX "Companion" Tag:** ``` https://get.truex.com/{placement_hash}/vast/companion?{ad_request_parameters} ``` **IDVx "Companion" Tag:** ``` https://get.truex.com/{placement_hash}/vast/idvx/companion?{ad_request_parameters} ``` **VAST Structure:** ```xml trueX 00:00:30 ``` ### Differentiating TrueX and IDVx The `` node value identifies the ad type: * **TrueX ads**: `trueX` * **IDVx ads**: `IDVx` Both tag types (Generic and Companion) work with both TrueX and IDVx products. --- --- url: /ads/publisher/platforms/vega.md --- # Vega (FireTV) Integration TrueX Ad Renderer for Amazon Vega (Kepler) OS provides a React Native component library for rendering interactive TrueX ads on FireTV devices. > \[!TIP] Reference App > For a complete working example, check out the **[TrueX Vega Reference App](https://github.com/socialvibe/truex-vega-reference-app)**. > \[!INFO] info AI Assistance > Integrating with an AI coding assistant? Use our machine-readable documentation context: > > * **[llms.txt](/llms.txt)**: Documentation index and summary. > * **[llms-full.txt](/llms-full.txt)**: Full documentation content. ## Overview The `@truex/ad-renderer-vega` package is a React Native component designed specifically for Amazon's Vega (Kepler) operating system, which powers FireTV devices. It enables publishers to deliver engaging interactive ad experiences that give viewers choice in their ad consumption. ## Key Features * **React Native Component**: Seamless integration with React Native applications * **TypeScript Support**: Full type definitions for type-safe development * **Event-Driven Architecture**: Comprehensive event system for ad lifecycle management * **WebView-Based Delivery**: Leverages Amazon's WebView capabilities for rich interactive content * **Production Ready**: Battle-tested in production environments ## Platform Requirements * **Target Platform**: Amazon Vega (Kepler) OS (FireTV devices) * **Framework**: React Native 0.72.0 * **Language**: TypeScript/JavaScript * **React Version**: 18.2.0 ## What You'll Need Before integrating TrueX ads into your Vega application, ensure you have: 1. A React Native application running on Amazon Vega OS 2. Access to Amazon Device SDKs (@amazon-devices/\* packages) - see [Amazon Vega SDK Documentation](https://developer.amazon.com/docs/fire-tv/vega-sdk.html) 3. A VAST configuration URL or VAST XML from your ad server 4. Basic understanding of React hooks and event handling ## Quick Example Here's a minimal example of integrating TrueX ads: ```tsx import React, { useCallback } from 'react'; import { TruexAd, TruexAdEvent, TruexAdEventType, TruexAdEventHandler } from '@truex/ad-renderer-vega'; // truexAdInfo contains the ad data from your VAST response function TruexAdContainer({ truexAdInfo, onTruexAdFinished, onTruexAdFreePod }) { const handleTruexAdEvent = useCallback( event: TruexAdEvent) => { switch (event.type) { case TruexAdEventType.AD_COMPLETED: case TruexAdEventType.AD_ERROR: case TruexAdEventType.NO_ADS_AVAILABLE: case TruexAdEventType.USER_CANCEL_STREAM: // Ad finished, resume content onTruexAdFinished(); break; case TruexAdEventType.AD_FREE_POD: // User earned ad credit onTruexAdFreePod() break; } }, [ onTruexAdFinished, onTruexAdFreePod ] ); // Parse the ad parameters from the VAST node const adParameters = JSON.parse(truexAdInfo.adParameters); return ( ); } ``` --- --- url: /ads/publisher/platforms/vega/getting-started.md --- # Getting Started This guide will help you add the TrueX Ad Renderer to your existing FireTV app's advertising setup. ## Installation Install the TrueX Ad Renderer for Vega and its required dependencies. :::steps 1. Install the Package Using npm: ```bash npm install --save @truex/ad-renderer-vega ``` 2. Peer Dependencies The TrueX Ad Renderer requires specific peer dependencies. Install all required packages: ```bash npm install \ @amazon-devices/react-native-device-info@~2.0.0 \ @amazon-devices/webview@~3.3.0 ``` 3. Verify Installation After installation, verify that the package is correctly installed: ```bash npm list @truex/ad-renderer-vega ``` You should see output similar to: ``` your-app@1.0.0 └── @truex/ad-renderer-vega@x.x.x ``` ::: ## Basic Usage Learn how to integrate the TrueX Ad Renderer component into your React Native application. ### Import the Component In your React Native component, import the TrueX Ad component: ```typescript import { TruexAd } from '@truex/ad-renderer-vega'; ``` ### Import Types For TypeScript projects, import the type definitions: ```typescript import { TruexAd, TruexAdEvent, TruexAdEventType, TruexAdRendererOptions, TruexAdProps } from '@truex/ad-renderer-vega'; ``` ### Minimal Example Here's the simplest way to render a TrueX ad: ```tsx import React, { useCallback } from 'react'; import { TruexAd, TruexAdEventType, TruexAdEventHandler } from '@truex/ad-renderer-vega'; function TruexAdContainer({ truexAdInfo, onTruexAdFinished, onTruexAdFreePod}) { const handleTruexAdEvent = useCallback( event: TruexAdEvent) => { switch (event.type) { case TruexAdEventType.AD_COMPLETED: case TruexAdEventType.AD_ERROR: case TruexAdEventType.NO_ADS_AVAILABLE: case TruexAdEventType.USER_CANCEL_STREAM: // Ad finished, resume content onTruexAdFinished(); break; case TruexAdEventType.AD_FREE_POD: // User earned ad credit onTruexAdFreePod() break; } }, [ onTruexAdFinished, onTruexAdFreePod ] ); // Parse the ad parameters from the VAST node const adParameters = JSON.parse(truexAdInfo.adParameters); return ( ); } export default TruexAdContainer; ``` ### Component Props The `TruexAd` component accepts the following props: #### Required Props * **`adParameters`**: An object containing the ad configuration. This object is obtained by parsing the JSON content of the `` node from the VAST/VMAP response. * **`onAdEvent`**: A callback function to handle ad lifecycle events. ```tsx // Parse the JSON string from the node in your VAST response const adParams = JSON.parse(adParametersStringFromVast); ``` #### Optional Props * **`options`**: Configuration options for the renderer. ```tsx import { TruexAdRendererOptions } from '@truex/ad-renderer-vega'; const options: TruexAdRendererOptions = { supportsUserCancelStream: true, appId: 'my-firetv-app', enableWebViewDebugging: __DEV__ }; ``` ### Configuration Options The `options` prop accepts a `TruexAdRendererOptions` object: ```typescript type TruexAdRendererOptions = { supportsUserCancelStream?: boolean, appId?: string, enableWebViewDebugging?: boolean, }; ``` *** #### `supportsUserCancelStream` * **Type**: `boolean` * **Default**: `false` * **Description**: Enables the `USER_CANCEL_STREAM` terminal event. When enabled, users can exit the entire stream from within the ad experience. ```tsx ``` *** #### `appId` * **Type**: `string` * **Default**: `undefined` * **Description**: Your application identifier for reporting and analytics. ```tsx ``` *** #### `enableWebViewDebugging` * **Type**: `boolean` * **Default**: `false` * **Description**: Enables WebView debugging tools. Should only be enabled in development builds. ```tsx ``` ## Runtime Version Information Access the TrueX Ad Renderer version at runtime: ```tsx import { buildInfo } from '@truex/ad-renderer-vega'; console.log(`Package: ${buildInfo.name}`); // Package: @truex/ad-renderer-vega console.log(`Version: ${buildInfo.version}`); // Version: 0.7.1 console.log(`Date: ${buildInfo.date}`); // Date: 2025-12-04T16:06:55.046Z ``` ## Understanding the Integration Flow Before diving into code, it's helpful to understand the integration workflow: 1. **Video Playback** - Your app plays video content 2. **Ad Break Detected** - Video player encounters ad break (pre-roll/mid-roll) 3. **Pause Video & Show TrueX Ad** - Pause playback, render TrueX component, hide controls 4. **User Interaction** - User engages with interactive ad experience 5. **Terminal Event Received** - Ad completion, error, or cancellation 6. **Resume Video Playback** - Hide ad, resume video, skip remaining ads if credit earned ## Key Integration Points Your integration will involve: 1. **Ad Break Detection**: Identifying when to show TrueX ads 2. **Component Rendering**: Displaying the `` component 3. **Event Handling**: Responding to ad lifecycle events 4. **Video Control**: Managing video playback state 5. **Ad Credit Tracking**: Honoring earned ad-free viewing ### What if I'm using a different video player? The TrueX Ad Renderer is video-player agnostic. You'll need to integrate it with your specific player's API for pausing/resuming playback. ### Is this only for FireTV? The `@truex/ad-renderer-vega` package is specifically designed for Amazon Vega (FireTV). Other platforms have their own integration packages: * [Web/HTML Integration](/platforms/web/) * [Roku Integration](/platforms/roku/) * [Android TV & Mobile Integration](/platforms/android/) * [tvOS Integration](/platforms/tvos/) --- --- url: /ads/publisher/platforms/vega/events.md --- # Event Handling The TrueX Ad Renderer uses an event-driven architecture to communicate ad lifecycle states to your application. ## Event Flow Diagram Typical event sequence for a successful ad interaction: ``` ◇ adStarted │ ↓ ◇ adFetchCompleted │ ↓ ◇ adDisplayed │ ↓ ◇ optIn (if user chooses interactive ad) │ ↓ ◆ [User interacts with ad] │ ↓ ◇ adFreePod (if user completes interaction) │ ↓ ◇ adCompleted ``` ## Event Handler Implementation ### Basic Event Handler ```tsx import { TruexAdEvent, TruexAdEventType, TruexAdEventHandler } from '@truex/ad-renderer-vega'; const [ shouldSkipRemainingAds, setShouldSkipRemainingAds ] = useState(false); const handleTruexAdEvent = useCallback( (event: TruexAdEvent) => { console.log('Event:', event.type); switch (event.type) { case TruexAdEventType.AD_COMPLETED: (shouldSkipRemainingAds) ? discardCurrentAdBreak() : startNextAdInCurrentAdBreak(); break; case TruexAdEventType.AD_ERROR: startNextAdInCurrentAdBreak(); break; case TruexAdEventType.NO_ADS_AVAILABLE: startNextAdInCurrentAdBreak(); break; case TruexAdEventType.USER_CANCEL_STREAM: exitPlaybackScreen(); break; case TruexAdEventType.AD_FREE_POD: setShouldSkipRemainingAds(true); break } }, [ shouldSkipRemainingAds ] ); ``` ### Type-Safe Event Handling TypeScript provides discriminated union types for type-safe event handling: ```tsx const handleTruexAdEvent = useCallback( (event: TruexAdEvent) => { switch (event.type) { case TruexAdEventType.AD_ERROR: // TypeScript knows errorMessage exists on this event logError(event.errorMessage); break; case TruexAdEventType.POPUP_WEBSITE: // TypeScript knows url exists on this event openBrowser(event.url); break; case TruexAdEventType.AD_COMPLETED: // TypeScript knows this event has only type property (and timeSpent) resumePlayback(); break; } }, [] ); ``` ## Terminal Events Terminal events indicate that the ad experience has concluded and your application should resume normal operation: *** ### `adCompleted` ```typescript { type: TruexAdEventType.AD_COMPLETED, timeSpent: number } ``` User successfully completed the interactive ad experience. Resume video playback. **Properties**: * `timeSpent`: Time spent in the ad experience in seconds. *** ### `adError` ```typescript { type: TruexAdEventType.AD_ERROR, errorMessage: string } ``` An error occurred during ad playback. Resume video playback and log the error. **Properties**: * `errorMessage`: Human-readable error description *** ### `noAdsAvailable` ```typescript { type: TruexAdEventType.NO_ADS_AVAILABLE } ``` No ads are available to display. Resume video playback immediately. *** ### `userCancelStream` ```typescript { type: TruexAdEventType.USER_CANCEL_STREAM } ``` User chose to exit the stream entirely. Navigate away from video playback. **Note**: Only fired when `supportsUserCancelStream: true` is set in options. ## Informational Events Informational events provide status updates but don't require immediate action: *** ### `adStarted` ```typescript { type: TruexAdEventType.AD_STARTED } ``` Ad UI construction has begun. Consider showing a loading indicator. *** ### `adFetchCompleted` ```typescript { type: TruexAdEventType.AD_FETCH_COMPLETED } ``` Ad creative has been fetched and is ready to be presented. *** ### `adDisplayed` ```typescript { type: TruexAdEventType.AD_DISPLAYED } ``` Ad is fully loaded and displayed to the user. Hide loading indicators. *** ### `adFreePod` ```typescript { type: TruexAdEventType.AD_FREE_POD } ``` User has earned ad-free viewing credit. Skip remaining ads in the current pod. *** ### `optIn` ```typescript { type: TruexAdEventType.OPT_IN, userInitiated: boolean } ``` User chose to engage with the interactive ad experience. **Properties**: * `userInitiated` - `true` if actively selected by user, `false` if choice card countdown expired. *** ### `optOut` ```typescript { type: TruexAdEventType.OPT_OUT, userInitiated: boolean } ``` User declined to engage with the interactive ad. **Properties**: * `userInitiated`: `true` if actively selected by user, `false` if choice card countdown expired. *** ### `userCancel` ```typescript { type: TruexAdEventType.USER_CANCEL } ``` User backed out of the ad but did not exit the stream. **Properties**: * `url`: The URL to open in an external browser ## Helper Functions ### `isTerminalEvent` Determines if an event is a terminal event. ```typescript function isTerminalEvent(event: TruexAdEvent): boolean ``` **Parameters**: * `event` - The event to check **Returns**: `true` if the event is terminal, `false` otherwise **Example**: ```tsx import { isTerminalEvent } from '@truex/ad-renderer-vega'; const handleTruexAdEvent = (event: TruexAdEvent) => { if (isTerminalEvent(event)) { // Handle terminal event hideAd(); resumeVideo(); } }; ``` ## Checking for Terminal Events Use the helper function to determine if an event is terminal: ```tsx import { TruexAdEvent, isTerminalEvent } from '@truex/ad-renderer-vega'; const handleTruexAdEvent = (event: TruexAdEvent) => { console.log('Event:', event.type); if (isTerminalEvent(event)) { // This is a terminal event - resume app hideAd(); resumeVideo(); } }; ``` ## Best Practices ### Always Handle All Terminal Events ```tsx const handleTruexAdEvent = (event: TruexAdEvent) => { if (isTerminalEvent(event)) { cleanupAd(); resumeApp(); } }; ``` ### Track Ad Credits ```tsx case TruexAdEventType.AD_COMPLETED: // Skip remaining ads in this pod if `adFreePodEearnd` is true adFreePodEarned ? skipRemainingAds() : startNextAdInCurrentAdBreak() ; break; case TruexAdEventType.AD_FREE_POD: // Mark that user earned ad-free viewing // and call `skipRemainingAds` on `adCompleted` setAdFreePodEarned(true); break; ``` ### Use Callbacks Properly Wrap event handlers in `useCallback` to prevent unnecessary re-renders: ```tsx const handleTruexAdEvent = useCallback((event: TruexAdEvent) => { // Handler implementation }, [/* dependencies */]); ``` --- --- url: /ads/publisher/platforms/web/truex.md --- # TrueX ### Product Flow **TrueX** presents users with an **interactive choice card** where they choose between engaging with branded content or watching standard video ads. When users opt in and complete the interactive experience, they earn an **ad credit that skips all remaining ads** in the pod, creating a win-win: viewers get ad-free content, and advertisers get highly engaged audiences. **Key features:** * **Opt-in via choice card** - Users actively choose to engage * **Engagement requirements** - Users must spend 30 seconds and interact at least once to earn credit * **Skips entire ad break** - Successful engagement bypasses all remaining ads in the pod Upon completion, the renderer signals a `adFreePod` event, indicating that remaining linear ads should be skipped. There are two distinct product flows supported by `TruexAdRenderer`: Sponsored Stream (full-stream ad-replacement) and Sponsored Ad Break (mid-roll ad-replacement). In a Sponsored Ad Break flow, once the user hits a mid-roll break with a **TrueX** tag flighted, they will be shown a "choice-card" offering them the choice between watching a normal set of video ads or a fully interactive **TrueX** ad: ![choice card](/assets/images/choice_card.png) ***Fig. A** example **TrueX** mid-roll choice card* If the user opts for a normal ad break, or if the user does not make a selection before the countdown timer expires, the **TrueX** UI will close and playback of normal video ads can continue as usual. If the user opts to interact with **TrueX**, an interactive ad unit will be shown to the user: ![ad](/assets/images/ad.png) ***Fig. B** example **TrueX** interactive ad unit* The requirement for the user to "complete" this ad is for them to spend at least the allotted time on the unit and for at least one interaction (e.g. navigating anywhere through the ad). ![true attention timer example](/assets/images/true-attention-timer-example.png) ***Fig. C** example **TrueX** attention timer* Once the user fulfills both requirements, a "Watch Your Show" button will appear in the bottom right, which the user can select to exit the **TrueX** ad. Having completed a **TrueX** ad, the user will be returned directly to content, skipping the remaining ads in the current ad pod. The Sponsored Stream flow is quite similar. In this scenario, a user will be shown a choice-card in the pre-roll: ![choice card](/assets/images/choice_card.png) ***Fig. D** example **TrueX** pre-roll choice card (full-stream replacement)* Similarly, if the user opts-in and completes the **TrueX** ad, they will be skipped over the remainder of the pre-roll ad break. However, every subsequent mid-roll break in the current stream will also be skipped over. In this case instead of the regular pod of video ads, the user will be shown a "hero card" (also known as a "skip card"): ![skip card](/assets/images/skip_card.png) ***Fig. E** example **TrueX** mid-roll skip card* This messaging will be displayed to the user for several seconds, after which they will be returned directly to content. --- --- url: /ads/publisher/platforms/web/idvx.md --- # Interactive Video (IDVx) ### How Interactive Video (IDVx) Works **Interactive Video (IDVx)** delivers a new industry standard in video engagement across all devices and platforms. The product goes beyond hovers and inadvertent clicks, to deliver real interaction, by real humans, in premium environments. Built on the same core interactive foundation as TrueX, Interactive Video removes the opt-in, interaction, and time spent requirements, in order to capitalize on the scale of a Publisher's inventory. Infillion pays Publishers a set CPM for the IDVx ads the Publisher renders in their ad pods. **Key features:** * **Automatic start** - Begins playing without opt-in; there is no choice card * **Interactive throughout** - Users can interact with ad content during playback, but no interaction is required * **Capped at 30 seconds** - Unlike the unlimited time a user can spend in a TrueX ad * **Plays inline** - Completes and continues to the next ad in the pod; it does not skip or suppress the Publisher's ad pod ![Interactive Video ad pod flow](/assets/images/image5.jpg) ***Fig. A** example of an IDVx ad playing inline within a regular ad pod* #### User Flow 1. A viewer is streaming video content and reaches a commercial break 2. The content is paused to load the ad pod 3. The IDVx ad is loaded like any other ad, and is one of many in the Publisher's ad pod 4. When the user comes across the IDVx ad, they can interact with it if they choose 5. IDVx ads are 30 seconds long, so when those 30 seconds are up the ad ends 6. The video player then loads either another ad or the video content #### Supported Platforms IDVx ads are available on the following platforms: * [Vega (FireTV)](/platforms/vega/) * [Web/HTML](/platforms/web/) - desktop browsers and HTML5-based CTV apps * [Roku](/platforms/roku/) * [Android](/platforms/android/) - TV & mobile * [iOS](/platforms/ios/) * [tvOS](/platforms/tvos/) - coming soon ## Flighting the IDVx Placement Tag The IDVx placement tag is a 1 stage tag and can be flighted in every ad slot of a pod (except any reserved for TrueX) according to the priority the Publisher wants to give Infillion in filling that slot. For example the Publisher may wish to put the IDVx tag behind direct sold campaign tags, but ahead of programmatic ad tags. Or the Publisher may wish to give the IDVx tag the first position in every last slot of every pod. :::steps 1. The Publisher's AdOps team schedules the IDVx placement tag in any priority they wish throughout their network in their ad server 2. If the Publisher already has the TrueX SDK installed, they need to make a few changes to the integration code ([see below](#changes-existing-truex-publishers-need-to-make-to-support-idvx-ads)) 3. When the ad pod is triggered by the user, the IDVx ad tag will request an ad from the Infillion ad server (we support both CSAI and SSAI) 4. If a match is found for the user, the Infillion ad server will send an IDVx ad response that will trigger the IDVx user flow detailed above 5. If no IDVx ad is matched for the user by the Infillion ad server, an empty VAST response will be passed back, triggering whatever is scheduled next - either another video ad or the content ::: ## Changes existing TrueX Publishers need to make to support IDVx ads If your HTML5 app already renders TrueX ads: 1. Allow the renderer to run for ads with `` `IDVx` in any pod position. The "first ad in the pod" rule applies to TrueX only. 2. Construct the renderer with the `adParameters` JSON from the VAST response - see [SDK Integration](./sdk-integration#constructor). 3. After an IDVx ad finishes, always continue with the next ad in the pod. IDVx never fires `adFreePod` - see [Integration Flow](./integration-flow#handling-terminal-events). ## Creative Example This animated gif illustrates the interactivity of an IDVx ad creative. ![Interactive Video creative example](/assets/images/image3.gif) > \[!TIP] > The [Infillion Engagement Showcase](https://engagements.infillion.com/showcase/) shows working examples of TrueX and IDVx ads on all supported devices and platforms. If the ad experience does not load right away in the demo environment, refresh the page and it should load properly. --- --- url: /ads/publisher/platforms/web.md --- # HTML5 Integration The TrueX Ad Renderer (TAR) for HTML5 renders Infillion interactive ads - [TrueX](./truex) and [IDVx](./idvx) - inside web sites and web apps that play long-form video with ad breaks, known as FEP (Full Episode Player) apps. It leverages your app's existing ad server and content delivery mechanism, for example SSAI. TAR is the npm package `@truex/ad-renderer`. Your app creates, initializes, and displays a `TruexAdRenderer` instance. The renderer requests the ad from Infillion, builds the UI (the TrueX choice card and the interactive ad unit), and emits events when your app needs to act. Your app still parses the ad response, detects when an Infillion ad is due, pauses the stream, creates the renderer, and handles its events. It is also responsible for skipping the ads in the current pod when the renderer says so. This is also the base renderer for the native SDKs: the Android, iOS, and Vega SDKs wrap it. See [Choose Your Integration](../). > \[!TIP] Reference Apps > For complete working examples, see the [Examples](./examples) page. ## Supported Devices The renderer has a class for each device type - see [Choosing the Renderer Class](./sdk-integration#choosing-the-renderer-class): * Connected TV: HTML5-based CTV apps, web-enabled smart TVs, and game consoles - `TruexAdRendererCTV` * Desktop browsers - `TruexAdRendererDesktop` * Mobile browsers and webviews - `TruexAdRendererMobile` ## TrueX and IDVx Both ad products are requested with a VAST tag in one of two formats, generic or companion - see [VAST Tag Types](./vast-tags). The same renderer renders both, and your app tells them apart by the VAST `` value: | | [TrueX](./truex) | [IDVx](./idvx) | | --- | --- | --- | | `` | `trueX` | `IDVx` | | Entry | Opt-in through a choice card | Starts automatically | | Pod position | First ad in the pod only | Any slot | | Reward | `adFreePod` - the remaining ads in the pod are skipped | None - the pod continues | ## What You'll Need * A web app with a video player and an ad integration (Google IMA CSAI, Google IMA DAI, another SSAI vendor, or your own ad layer) * The `@truex/ad-renderer` package - see [SDK Integration](./sdk-integration#adding-the-sdk) * A way to show a full-screen element above your video player where the renderer can show the interactive ad * A test VAST tag from your Infillion contact - see [Integration Flow](./integration-flow) ## Next Steps * [Integration Flow](./integration-flow) - what Infillion provides, and what your app has to build * [SDK Integration](./sdk-integration) - adding the package, choosing the renderer class, and calling `init` and `start` * [Events](./events) - every event the renderer emits * [Examples](./examples) - reference apps for CSAI, Google IMA CSAI, and Google IMA SSAI --- --- url: /ads/publisher/platforms/web/vast-tags.md --- # VAST Tag Types Infillion uses two types of VAST tags for both TrueX and Interactive Video products. The tags differ in how the `adParameters` JSON payload is embedded in the VAST XML. ### "Generic" Tag The `adParameters` are embedded directly as a JSON string within the `` node: **TrueX "Generic" Tag:** ``` https://get.truex.com/{placement_hash}/vast/generic?{ad_request_parameters} ``` **Interactive Video "Generic" Tag:** ``` https://get.truex.com/{placement_hash}/vast/idvx/generic?{ad_request_parameters} ``` **VAST Structure:** ```xml trueX 00:00:30 ``` ### "Companion" Tag The `adParameters` are embedded in a companion ad's `` node as base64-encoded json string: **TrueX "Companion" Tag:** ``` https://get.truex.com/{placement_hash}/vast/companion?{ad_request_parameters} ``` **IDVx "Companion" Tag:** ``` https://get.truex.com/{placement_hash}/vast/idvx/companion?{ad_request_parameters} ``` **VAST Structure:** ```xml trueX 00:00:30 ``` ### Differentiating TrueX and IDVx The `` node value identifies the ad type: * **TrueX ads**: `trueX` * **IDVx ads**: `IDVx` Both tag types (Generic and Companion) work with both TrueX and IDVx products. ### VPAID Tag The [VPAID integration](./vpaid) uses a third tag format. The generic and companion tags carry the `adParameters` JSON for the TrueX Ad Renderer. The VPAID tag points your client-side ad manager at Infillion's VPAID unit instead, and the unit loads everything else on its own. It works for both TrueX and IDVx. The tag is a VAST response with one linear creative. The `` is a JavaScript file with `apiFramework="VPAID"`, and the `` node holds a small JSON payload that the unit reads: ``` https://get.truex.com/{placement_hash}/vast ``` ```xml trueX 00:00:30 ``` Pass the tag to your ad manager as-is. #### Flighting Infillion ads are flighted in the ad pod by the tag's position. TrueX is offered in the first slot of a pod, so that a user who completes it can skip the rest of the pod. IDVx can be in any slot of the pod, and never skips ads. | Goal | Where to put the TrueX tag | | --- | --- | | Mid-roll replacement | The first slot of each mid-roll pod you want to replace. Pre-roll and the other mid-rolls are unaffected | | Full-stream replacement | The first slot of the pre-roll pod, and of every mid-roll pod after it | | Both on the same placement | Same as full-stream replacement | In full-stream replacement, a user who opts into the pre-roll unit and completes it skips the video ads in the later pods. The TrueX tags in those pods are still played - they skip the ads, and may show a "skip card" to the user. The unit then fires `AdFreePod` followed by `AdStopped`, and the user goes back to the content. --- --- url: /ads/publisher/platforms/web/integration-flow.md --- # Integration Flow An HTML5 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. :::steps 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](./vast-tags). 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 can be placed in any slot. 3. The publisher integrates the SDK Your app [resolves `adParameters`](#resolving-adparameters) from the ad, [starts the renderer](#starting-the-renderer) when the ad plays, and [handles the terminal events](#handling-terminal-events). ::: ## Resolving `adParameters` The renderer is constructed with an `adParameters` object. Where that JSON comes from depends on the VAST tag format and the ad framework your app uses: | Tag format | Where `adParameters` is in the VAST | Google IMA (CSAI and DAI) | Custom VAST parsing | | --- | --- | --- | --- | | Generic | `` as a JSON string | `ad.getTraffickingParametersString()` | Text content of `` | | Companion | `` as a base64 JSON `data:` URL | Use the generic tag with Google IMA | Text content of ``, base64-decoded after the comma | Some ad vendors, such as Verizon / Uplynk, return the ad schedule in a JSON object from their own API instead of a VAST response. With these vendors, `adParameters` is part of that response - work with your Infillion contact to identify it. First, identify the ad by its `` value (`ad.getAdSystem()` in Google IMA), compared case-insensitively. `trueX` is a TrueX ad and `IDVx` is an IDVx 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. 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 `` 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 same parser reads both formats - the companion's `data:` URL and the plain `` JSON: ```javascript // Reads `adParameters` from a `data:application/json;base64,...` URL or from plain JSON text. // Returns undefined unless the result is a JSON object. function parseAdParameters(resource) { let adParameters; try { const text = resource.trim(); let json = text; if (text.toLowerCase().startsWith('data:')) { // The ad server may wrap the base64 payload across lines, so whitespace is removed before decoding. const base64 = text.replace(/\s+/g, '').split(',')[1]; json = atob(base64); } const value = JSON.parse(json); if (value && typeof value === 'object' && !Array.isArray(value)) { adParameters = value; } } catch (err) { console.error('Could not parse adParameters: ' + err); } return adParameters; } ``` With Google IMA, pass `ad.getTraffickingParametersString()` to `parseAdParameters`. ## 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: * CSAI and custom ad layers: the placeholder plays as a short video, so it can be skipped when the renderer is done. * SSAI: the placeholder is stitched into the stream, so remember where it ends (the placeholder start plus its duration) to seek there later. 3. Create the renderer class for your device - see [Choosing the Renderer Class](./sdk-integration#choosing-the-renderer-class) - with the `adParameters` object, and subscribe to its events. 4. Call `init` and then `start`. To save loading time, `init` can be called earlier, before the ad break is reached. 5. Wait for a terminal event. While you wait, keep track of whether `adFreePod` has fired. The calls are described in [SDK Integration](./sdk-integration). ### Device Differences | | CTV | Desktop and mobile | | --- | --- | --- | | Renderer class | `TruexAdRendererCTV` | `TruexAdRendererDesktop` or `TruexAdRendererMobile` | | Input | The renderer maps remote and game controller keys to actions. Use `keyMapOverrides` on a device that is not supported yet | Mouse and touch | | Main video | Hide player controls. On constrained devices, unload the main video if possible and resume it when the ad is done | Hide player controls, status bars, and soft keyboards | | `popupWebsite` | Not used | The app must open the page (mobile only) - see [`popupWebsite`](./events#popupwebsite) | On CTV, if your app handles `history.back()`, add a `popstate` handler that only reacts to your own app's history state, so the renderer's back-action blocking keeps working while an ad is shown. On Fire TV, the back key cannot be reliably overridden, so `popstate` is the only option. The [Examples](./examples) show this in `onBackAction`. ## 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 result | What to do | Client-side ads (CSAI) | Server-side ads (SSAI) | | --- | --- | --- | --- | | `adCompleted` after `adFreePod` | Skip all remaining ads in the pod and continue the content | Skip the rest of the ad break, then resume the content | Seek past the end of the current ad break | | `adCompleted` without `adFreePod` | Skip the Infillion placeholder ad and play the next ad in the pod | Play the next ad in the pod | Seek to just before the placeholder's end, so the next stitched ad plays | | `adError` or `noAdsAvailable` | Same as `adCompleted` without `adFreePod` | Same | Same | | `userCancelStream` | The user closed the Infillion ad because they want to leave - exit the playback screen | Close the player | Close the player | `userCancelStream` is only fired when the renderer is created with the `supportsUserCancelStream` option; otherwise a back action ends with `adCompleted`. `adCompleted` without `adFreePod` covers a TrueX user who opted out or let the choice card time out, and it is the normal outcome of every IDVx ad, because IDVx never fires `adFreePod`. > \[!IMPORTANT] > Do not seek or resume playback when `adFreePod` fires. Remember that it fired, and act only on the terminal event that follows. Skipping the remaining video ads is the responsibility of your app. Ad SDKs such as Google IMA provide enough information to find where the current pod ends; when the user earned the reward, fast-forward directly to that point when resuming playback. After any terminal event, remove the ad overlay (the element that `start` resolves to) and drop all references to the renderer instance. The full list of terminal events is on the [Events](./events#terminal-events) page. --- --- url: /ads/publisher/platforms/web/sdk-integration.md --- # SDK Integration ## Adding the SDK The TrueX Ad Renderer is published on npm as `@truex/ad-renderer`. For a web app built around a `package.json`, add the dependency: ```sh npm add @truex/ad-renderer@1.14.6 --save-exact ``` This adds an entry to the `"dependencies"` section of `package.json`, for example: ```json "dependencies": { "@truex/ad-renderer": "1.14.6" } ``` Then build and run your web app as usual, for example with `npm start` for webpack-based projects. Alternatively, load the library with a script tag: ```html ``` To always load the latest version: ```html ``` ## Choosing the Renderer Class The library has one renderer class for each device type, so that the correct ad tracking is used: | Class | Use for | | --- | --- | | `TruexAdRendererCTV` | Connected TV apps, web-enabled smart TVs, and game consoles | | `TruexAdRendererDesktop` | Desktop browsers | | `TruexAdRendererMobile` | Mobile browsers and mobile webviews | The deprecated `TruexAdRenderer` class maps to `TruexAdRendererCTV`. All three classes have the same methods and events, described on this page and on the [Events](./events) page. ## Quick Example The renderer is created with the `adParameters` JSON from the VAST response - see [Resolving `adParameters`](./integration-flow#resolving-adparameters). The example shows the CTV class; for desktop or mobile, use `TruexAdRendererDesktop` or `TruexAdRendererMobile`: ```javascript import { TruexAdRendererCTV, TruexAdEventType } from '@truex/ad-renderer'; videoController.pause(); let adFreePod = false; const tar = new TruexAdRendererCTV(adParameters); tar.subscribe(handleTruexAdEvent); try { await tar.init(); await tar.start(); } catch (err) { console.error('ad error: ' + err); tar.stop(); resumePlayback(); } function handleTruexAdEvent(event) { switch (event.type) { case TruexAdEventType.AD_FREE_POD: adFreePod = true; // the user did sufficient interaction for an ad credit break; case TruexAdEventType.AD_ERROR: console.error('ad error: ' + event.errorMessage); resumePlayback(); break; case TruexAdEventType.NO_ADS_AVAILABLE: case TruexAdEventType.AD_COMPLETED: // The ad is not available, or has completed. Depending on the adFreePod flag, either the main // video or the ad fallback videos are resumed. resumePlayback(); break; } } function resumePlayback() { if (adFreePod) { // The user has the credit, resume the main video after the ad break. videoController.skipAdBreak(); videoController.resumeMainVideo(); } else { // Continue with playing the remaining fallback ad videos in the ad break. videoController.skipCurrentAd(); videoController.resumeAdBreak(); } } ``` ## `TruexAdRenderer` Methods ### `constructor` ```typescript constructor(parameters: TruexAdRendererParameters, options?: TruexAdRendererOptions) ``` `parameters` is one of: * the `adParameters` object from the VAST response - the usual case, e.g. `const tar = new TruexAdRendererCTV(adParameters)`, see [Resolving `adParameters`](./integration-flow#resolving-adparameters) * a VAST config URL string * an object with a `vast_config_url` field and an optional `user_id` `options` are optional overrides: | Option | Type | Description | | --- | --- | --- | | `showLoadingSpinner` | `boolean` | If `true` (default), a loading indicator is shown while the choice card display elements load, i.e. while waiting for image assets | | `userAdvertisingId` | `string` | The id used for user ad tracking. If omitted, the `network_user_id` in the VAST config URL is used, or else a random id | | `supportsUserCancelStream` | `boolean` | If `true`, enables the [`userCancelStream`](./events#usercancelstream) event for back actions from the choice card. Defaults to `false`, which means back actions cause `optOut` or `adCompleted` events instead | | `useHistoryBackActions` | `boolean` | Overrides the platform's auto-detected history API back handling. Omit it to keep the default. `false` handles the raw back key (LG apps with `disableBackHistoryAPI: true`). `true` waits for `popstate` | | `appId` | `string` | The application id or package name, for tracking | | `useIntegration` | `{ name, version }` or an array of them | The host integration name and version, for tracking. An array tracks multiple integrations | | `useWebMAF` | `boolean` | If `true` and running on the PS4, all ad videos are played via the WebMAF API, to work around WebMAF load failures caused by creating HTML5 video elements | | `keyMapOverrides` | `object` | Key codes for each input action, overriding the platform's built-in key mapping. Intended for platforms that TAR does not support yet | `keyMapOverrides` has the fields `select`, `back`, `moveUp`, `moveDown`, `moveLeft`, and `moveRight`, each a key code or an array of key codes: ```javascript { select: [32, 13], // space or enter back: [27, 8, 127], // esc or backspace or del moveUp: 38, moveDown: 40, moveLeft: 37, moveRight: 39 } ``` The renderer also has a read-only `version` field with the TAR build version. ### `init` ```javascript /** * Queries for the vast config result * * @returns {Promise} resolves to the resulting the vast config result object */ function init() ``` This should be called by the host app code in order to initialize the `TruexAdRenderer` instance. The renderer will use the VAST config url passed to it in the constructor and make a request to the TrueX ad server to see what ads are available. You may initialize `TruexAdRenderer` early (a few seconds before the next pod even starts) in order to give it extra time to make the ad request. The renderer will output an `adFetchCompleted` event at completion of this ad request. This event can be used to facilitate the implementation of a timeout or loading indicator, and when to make the call to `start`. ### `start` ```javascript /** * Starts the choice card and engagement flow * * @param {Object} [vastConfig] optional defaults to the value obtained via the init() call. * * @param {HTMLElement} [parentElement] parent DOM element to add the ad overlay/choice card to. * Defaults to document.documentElement. * * @return {Promise} promise that completes when the choice card loads, resolves to the div holding the ad overlay. */ function start(vastConfig, parentElement) ``` This should be called by the host app code when the app is ready to display the TrueX unit to the user. This can be called anytime after the renderer is initialized. The host app should have as much extraneous UI hidden as possible, including player controls, status bars and soft buttons/keyboards. On constrained devices, especially connected TV devices, it is preferable to also unload the main video if possible, resuming it after the ad unit is completed. After calling `start`, the host app code should wait for a [terminal event](./events#terminal-events) before taking further actions, while keeping track of whether the [`adFreePod`](./events#adfreepod) event has fired. In a non-error flow, the renderer will first wait for the ad request triggered in `init` to finish if it has not already. It will then display the TrueX ad unit to the user in a new component (added to the `TruexAdRenderer` parent component) and then fire the [`adStarted`](./events#adstarted) event. `adFreePod` and other events may fire after this point, depending on the user's choices, followed by one of the [terminal events](./events#terminal-events). ### `stop` ```javascript /** * Allow ads to be forcibly closed by the host app or test script. * (Needs to be possible if the app needs to reset, otherwise back actions can be permanently * blocked if the ad is not cleaned up.) */ function stop() ``` The `stop` method is only called when the host app needs the renderer to immediately stop and destroy all related resources. It is not needed after a terminal event, when the renderer has already finished - just remove the ad overlay and the references to the renderer. Examples include: * the user backs out of the video stream to return to the normal app UI * there was an unrelated error that requires immediate halt of the current ad unit * a call to `init` or `start` failed * the app code has reached a custom timeout waiting for either the [`adFetchCompleted`](./events#adfetchcompleted) or [`adStarted`](./events#adstarted) events The renderer instance should not be used again after calling `stop` -- please remove all references to it afterwards. ### `pause` ```javascript /** * Pauses an ad if the hosting app needs to suspend/resume itself. * I.e. Any ad videos are paused and countdowns suspended. */ function pause() ``` ### `resume` ```javascript /** * Resumes a previously paused ad. I.e. any paused ad videos are resumed, as well as suspended countdowns. */ function resume() ``` ### `subscribe` ```typescript subscribe(callback: (event: TruexAdEvent) => unknown): void subscribe(type: TruexAdEventName, callback: (event) => unknown): void ``` An ad event object has a `type` field that describes which event has occurred - one of the [`TruexAdEventType`](./events) constants - along with other data fields depending on the event. Use `subscribe` to listen to ad events emitted from the renderer. The key ones the host app should listen to are: ```javascript adFreePod adCompleted noAdsAvailable adError ``` To listen to all ad events, just pass in a single callback as the argument, e.g. `tar.subscribe(handleAdEvent)`. To listen to a particular ad event, pass in the type to listen to and the callback, e.g. `tar.subscribe('adCompleted', handleAdCompleted)` The ad event names are available as the `TruexAdEventType` constants exported by the package, e.g. `TruexAdEventType.AD_FREE_POD`. The renderer's `adEvents` field is deprecated. ### `unsubscribe` ```typescript unsubscribe(callback: (event: TruexAdEvent) => unknown): void unsubscribe(type: TruexAdEventName, callback: (event) => unknown): void ``` Removes a callback that was passed to `subscribe`, with the same arguments. ### `setAdVolume` ```typescript setAdVolume(volume: number): void ``` Sets the choice card voiceover volume, from 0 to 100. --- --- url: /ads/publisher/platforms/web/events.md --- # Events `TruexAdRenderer` tells your app about the ad lifecycle through ad events. Subscribe to them with [`subscribe`](./sdk-integration#subscribe), using the event names below or the matching `TruexAdEventType` constants (for example `TruexAdEventType.AD_FREE_POD` for `adFreePod`). Four events are terminal, and `adFreePod`, `userCancelStream`, and `popupWebsite` need action from your app. | Event | Needs action | Terminal | | --- | --- | --- | | [`adFetchCompleted`](#adfetchcompleted) | | | | [`adStarted`](#adstarted) | | | | [`adDisplayed`](#addisplayed) | | | | [`adCompleted`](#adcompleted) | Yes | Yes | | [`adError`](#aderror) | Yes | Yes | | [`noAdsAvailable`](#noadsavailable) | Yes | Yes | | [`adFreePod`](#adfreepod) | Yes | | | [`userCancelStream`](#usercancelstream) | Yes | Yes | | [`popupWebsite`](#popupwebsite) | Mobile only | | | [`optIn`](#optin) | | | | [`optOut`](#optout) | | | | [`skipCardShown`](#skipcardshown) | | | | [`userCancel`](#usercancel) | | | | [`xtendedViewStarted`](#xtendedviewstarted) | | | | [`videoEvent`](#videoevent) | | | ## Event Flow A typical TrueX ad where the user opts in and earns the reward: ``` ◇ adFetchCompleted │ ↓ ◇ adStarted │ ↓ ◇ adDisplayed │ ↓ ◇ optIn │ ↓ ◆ [User interacts with the ad] │ ↓ ◇ adFreePod │ ↓ ◇ adCompleted ``` An IDVx ad has no choice card and no reward, so it goes from `adDisplayed` straight to `adCompleted`, without `optIn`, `optOut`, or `adFreePod`. ## Terminal Events A terminal event means the renderer is done, and your app must continue playback - see [Handling Terminal Events](./integration-flow#handling-terminal-events) for what to do in each case: * [`adCompleted`](#adcompleted) - the ad is finished * [`adError`](#aderror) - the renderer hit an unrecoverable error * [`noAdsAvailable`](#noadsavailable) - there is no ad for this user * [`userCancelStream`](#usercancelstream) - the user wants to exit the stream After a terminal event, remove the ad overlay (the element that [`start`](./sdk-integration#start) resolves to) and all references to the renderer instance. [`stop`](./sdk-integration#stop) is only needed to close an ad before a terminal event. ## Main Flow Events The following events signal the main flow of the `TruexAdRenderer` and may require action by the host app: ### `adFetchCompleted` This event fires in response to the `init` method when the TrueX ad request has successfully completed and the ad is ready to be presented. The host app may use this event to facilitate a loading screen for pre-rolls, or to facilitate an ad request timeout for mid-rolls. For example: `init` is called for the pre-roll slot, and the host app code shows a loading indicator while waiting for `adFetchCompleted`. Then, either the event is received (and the app can call `start`) or a specific timeout is reached (and the app can discard the `TruexAdRenderer` instance and resume playback of normal video ads). Another example: `init` is called well before a mid-roll slot to give the renderer a chance to preload its ad. If `adFetchCompleted` is received before the mid-roll slot is encountered, then the app can call `start` to immediately present the TrueX ad. If not, the app can wait for a specific timeout (if not already reached, in case the user has sought to activate the mid-roll) before removing the `TruexAdRenderer` instance and resuming playback with normal video ads. ### `adStarted` This event fires after the `start` call, when the TrueX UI is being constructed and connected to the web page. ### `adDisplayed` This event fires once the TrueX UI is loaded and visible, i.e. all image assets are loaded, etc. This is useful if the host app is implementing its own loading indiciator UX (i.e. its own loading spinner), and needs to know when the ad UI is actually ready to be disabled without delays. ### `adCompleted` This is a [terminal event](#terminal-events). This event will fire when the TrueX unit is finished with its activities \-- at this point, the app should resume playback and remove the TrueX ad renderer from its UI Here are some examples where `adCompleted` will fire: * The user opts for normal video ads (not TrueX) * The choice card timeout runs out * The user completes the TrueX ad unit * After a "skip card" has been shown to a user for its duration The event data field for this event is: * `timeSpent`: The amount of time (in seconds) the user spent on the TrueX unit ### `adError` This is a [terminal event](#terminal-events). This event will fire when the TrueX unit has encountered an unrecoverable error. The host app code should handle this the same way as an `adCompleted` event -- resume playback and remove the TrueX ad renderer from its UI. The event data field for this event is: * `errorMessage`: A description of the cause of the error. ### `noAdsAvailable` This is a [terminal event](#terminal-events). This event will fire when the TrueX unit has determined it has no ads available to show the current user. The host app code should handle this the same way as an `adCompleted` event - resume playback and remove the TrueX ad renderer from its UI. ### `adFreePod` This event will fire when the user has earned a credit with TrueX. The host app code should notate that this event has fired, but should not take any further action. Upon receiving a [terminal event](#terminal-events), if `adFreePod` was fired, the app should skip all remaining ads in the current slot. If it was not fired, the app should resume playback without skipping any ads, so the user receives a normal video ad payload. ### `userCancelStream` This is a [terminal event](#terminal-events), and is only enabled when the `supportsUserCancelStream` property is set to `true` in the [constructor options](./sdk-integration#constructor). When enabled, the renderer will fire this event when the user intends to exit the stream entirely. The app, at this point, should treat this the same way it would handle any other "exit" action from within the stream -- in most cases this will result in the user being returned to an episode/series detail page. If this event is not enabled, the renderer will emit an [`adCompleted`](#adcompleted) event instead. ## Informative Events All following events are used mostly for tracking purposes -- no action is generally required. ### `optIn` This event will fire if the user selects to interact with the TrueX interactive ad. Note that this event may be fired multiple times if a user opts in to the TrueX interactive ad and subsequently backs out. ### `optOut` This event will fire if the user opts for a normal video ad experience. The event data field for this event is: * `userInitiated`: This will be set to `true` if this was actively selected by the user, `false` if the user simply allowed the choice card countdown to expire. ### `skipCardShown` This event will fire anytime a "skip card" is shown to a user as a result of completing a TrueX Sponsored Stream interactive in an earlier pre-roll. ### `userCancel` This event will fire when a user backs out of the TrueX interactive ad unit after having opted in. This would be achieved by tapping the "Yes" link to the "Are you sure you want to go back and choose a different "ad experience" prompt inside the TrueX interactive ad. The user will be subsequently taken back to the Choice Card (with the countdown timer reset to full). Note that after a `userCancel`, the user can opt-in and engage with an interactive ad again, so more `optIn` or `optOut` events may then be fired. ### `popupWebsite` On mobile platforms, ad units with links to external web sites fire this event when the user clicks on the web site link, e.g. typically via a "Learn More" link. On mobile platforms, navigating to an external web site means leaving the current application. As such this ad event is used to communicate the user's intention, and the host app needs to open the external web page as appropriate for the platform. E.g. for Android, an intent is raised to open the web page in the phone's browser. The event data field for this event is: * `url`: Indicates the web page to navigate to. ### `xtendedViewStarted` This event fires for TrueX ad creatives that their own fallback extended videos built in. From the host app's perspective, nothing more is needed to be done, and the regular ad event flow processing will occur, just with the extended ad content instead. ### `videoEvent` This event reports noteworthy video progress inside the TrueX unit. The event data fields for this event are: * `subType`: One of `started`, `firstQuartile`, `secondQuartile`, `thirdQuartile`, or `completed` * `videoName`: The name of the video * `url`: The video URL, when available --- --- url: /ads/publisher/platforms/web/examples.md --- # Examples Three HTML5 reference repositories show four ways to integrate TrueX and IDVx ads into a CTV web app. The first repository contains two examples, one for each way ads are inserted: | Example | Repository | Ad source | Ad break behavior | | --- | --- | --- | --- | | [Manual CSAI](#manual-csai) | [truex-ctv-web-reference-app][web-repo] | Ad breaks defined in `video-streams-csai.json` | The app pauses the content, plays the ad pod itself, and resumes the content | | [Manual SSAI](#manual-ssai) | [truex-ctv-web-reference-app][web-repo] | Ad breaks defined in `video-streams-ssai.json` | The ads are stitched into one video stream, so the app calculates the ad break times | | [Google IMA CSAI](#google-ima-csai) | [truex-ctv-google-ima-csai-ref-app][csai-repo] | A VAST ad playlist (`sample-ad-playlist.xml`) | Google IMA requests and sequences client-side ads | | [Google IMA SSAI](#google-ima-ssai) | [truex-ctv-google-ima-ssai-ref-app][ssai-repo] | Google Ad Manager VAST tags stitched into the stream | One stream with stitched ad breaks, defined in `video-streams.json` | Each example has its own player, ad, and `TruexAdRenderer` code on purpose, so you can pick the one closest to your setup and read it from top to bottom. All of them are webpack apps that load the renderer from the `@truex/ad-renderer` package - see [SDK Integration](./sdk-integration#adding-the-sdk). > \[!NOTE] > The Google IMA apps may still construct the renderer from the VAST config URL as a legacy fallback. For new integrations, construct it from `adParameters` as described in [Integration Flow](./integration-flow#resolving-adparameters). The Manual examples already accept an `adParameters` JSON object for IDVx ads. ## Running the Apps Requirements: * The Node version in the repository's `.nvmrc` (14.16 at the time of writing) Run an app locally: ```bash npm install npm start ``` The app is served at `http://localhost:8080`. To build a deployable version in the `dist` folder, run `npm run build` and host its contents. To test on a device, open your computer's local network address (for example `http://192.168.1.72:8080`) in the device's browser, or use it as the launcher URL of the device's app. Each repository has platform-specific instructions under its `platforms` folder for Fire TV / Android TV, Vizio, LG, Tizen, PS4, PS5, and Xbox One. While the app is running: * Select "4" or Menu on the remote, or click the left stick on the controller, to show the in-app debug console. The back action closes it. * Select "2" on the remote, or click the right stick on the controller, to skip the current ad break (Manual examples). ## Shared Rules All the examples follow the same rules, described in [Integration Flow](./integration-flow): * An ad with `` `trueX` is a TrueX ad only when it is the first ad in the pod. An ad with `` `IDVx` can be in any position. * The content and ad playback are paused before the renderer is created. * Each example has an `InteractiveAd` component (`interactive-ad.js`) that creates the renderer, subscribes to its events, calls `init` and `start`, and removes the ad overlay when a terminal event arrives. * `adFreePod` only sets a flag. The playback timeline moves when the terminal event arrives: with the flag set, the app skips the rest of the ad break; without it, the app plays the next ad in the pod. * The renderer is created with `supportsUserCancelStream: true`, so a user who backs out of the choice card closes the video. * The back action is handled with a `popstate` handler that ignores the renderer's own history state (`onBackAction`, `pushBackActionBlock`, and `pushBackActionStub` in `main-csai.js`, `main-ssai.js`, or `main.js`). ## Manual CSAI Repository: [truex-ctv-web-reference-app][web-repo]. Open `index-csai.html` (`main-csai.js`, `src/components/csai/`), hosted at [ctv.truex.com/web/ref-app/master/index-csai.html](https://ctv.truex.com/web/ref-app/master/index-csai.html). The ad breaks are defined in `src/data/video-streams-csai.json`: * `timeOffset`: the content time when the ad break starts * `description`: the VAST config URL of a TrueX ad * `adParameters`: the JSON configuration of an IDVx ad * `mediaFile`: the fallback video ad Flow: 1. The `VideoController` receives the video stream in `startVideo`, and `setAdPlaylist` creates an `AdBreak` for each ad break. 2. When the content reaches a `timeOffset`, the content pauses and the ads play separately. No time adjustments are needed, because the ads are not part of the content timeline. 3. For a TrueX or IDVx ad, an `InteractiveAd` creates the renderer and starts it. 4. On a terminal event, `skipAdBreak` skips the rest of the ad break if the user earned `adFreePod`, or `playNextAdInBreak` plays the next ad. The content then resumes from the pause point. ## Manual SSAI Repository: [truex-ctv-web-reference-app][web-repo]. Open `index.html` (`main-ssai.js`, `src/components/ssai/`), hosted at [ctv.truex.com/web/ref-app/master/index.html](https://ctv.truex.com/web/ref-app/master/index.html). The ad breaks are defined under the `vmap` key of `src/data/video-streams-ssai.json`: * `timeOffset`: the content time of the ad break * `videoAdDuration`: the total length of the stitched ads (placeholders and fallback ads) in seconds * `ads`: the ads of the break, each with `adSystem` (`trueX`, `IDVx`, or `GDFP`), `duration`, and `description` (VAST config URL) or `adParameters` (JSON) Flow: 1. The SSAI `VideoController` calculates the `startTime` and `endTime` of each ad break from its `timeOffset` and the durations of all the earlier ads, because the ads are stitched into the timeline. It subtracts the ad durations from the raw video position to get the content time. 2. When playback reaches the start of an Infillion ad's placeholder, the app pauses and an `InteractiveAd` creates and starts the renderer. 3. On a terminal event, `skipAdBreak` jumps past the end of the ad break if the user earned `adFreePod`; otherwise `resumeAdBreak` continues with the next stitched ad. ## Google IMA CSAI Repository: [truex-ctv-google-ima-csai-ref-app][csai-repo]. It uses the [HTML5 Google IMA SDK](https://developers.google.com/interactive-media-ads/docs/sdks/html5/client-side) for client-side ad insertion, with two versions of the same app: | Version | Entry point | Hosted copy | | --- | --- | --- | | Plain IMA SDK with your own HTML5 video element | `src/simple/simple.js`, `simple-video-controller.js` | [index.html](https://ctv.truex.com/web/ref-app-IMA-CSAI/master/index.html) | | IMA with [video.js](https://videojs.com/) | `src/videojs/videojs-demo.js`, `videojs-controller.js` | [videojs-demo.html](https://ctv.truex.com/web/ref-app-IMA-CSAI/master/videojs-demo.html) | The content is defined in `src/data/video-streams.json`, and the ads in `src/data/sample-ad-playlist.xml`, with a preroll and a midroll ad break that can contain TrueX and IDVx ads. Flow: 1. The video controller fields the IMA ad events in `onAdEvent`. On `AdEvent.Type.STARTED`, `startInteractiveAd()` identifies the ad: an `` of `trueX` at pod position 1 is a TrueX ad, and `IDVx` is an IDVx ad. Any other ad keeps playing in the `adsManager`. 2. For an Infillion ad, the app creates an `InteractiveAd` (`src/components/interactive-ad.js`), which creates the renderer and starts it. The trafficking parameters come from the IMA ad object (`getTraffickingParametersString()`). 3. On a terminal event, `skipAdBreak()` discards the ad break if the user earned `adFreePod`; otherwise `resumeAdPlayback()` lets the `adsManager` play the next ad. ## Google IMA SSAI Repository: [truex-ctv-google-ima-ssai-ref-app][ssai-repo], hosted at [ctv.truex.com/web/ref-app-google-IMA/master/index.html](https://ctv.truex.com/web/ref-app-google-IMA/master/index.html). It uses Google Ad Manager to stitch the ads into the video stream. The content and its ad breaks, `preroll` and `midroll-1`, are defined in `src/data/video-streams.json`: * `timeOffset`: the content time where the ad break starts. The ads are stitched in, so the real video time is the content plus all the ad durations. * `videoAdDuration`: the length of the stitched fallback video * `vastUrl`: the VAST config URL of the Infillion ad Flow: 1. The `VideoController` (`src/components/video-controller.js`) receives the stream in `startVideo`, and `setAdPlaylist` creates an `AdBreak` for each entry of the `vmap` array. 2. When `onVideoTimeUpdate` reaches the start of an ad break, an `InteractiveAd` creates and starts the renderer over the playback page. 3. On a terminal event, the app seeks to the end of the placeholder if the user did not earn `adFreePod`, or to the end of the pod if they did, and playback continues. The VAST responses served to Google Ad Manager are hosted on dedicated endpoints - a change there is eventually picked up by Google Ad Manager. [web-repo]: https://github.com/socialvibe/truex-ctv-web-reference-app [csai-repo]: https://github.com/socialvibe/truex-ctv-google-ima-csai-ref-app [ssai-repo]: https://github.com/socialvibe/truex-ctv-google-ima-ssai-ref-app --- --- url: /ads/publisher/platforms/web/vpaid.md --- # VPAID Integration VPAID is the integration path for web sites and web apps that play ads through a client-side ad manager - for example Google IMA, or the built-in ad manager of a player such as Brightcove or Bitmovin. The ad manager loads Infillion's VPAID unit like any other VPAID ad, so you do not use the `@truex/ad-renderer` package or write any initialization code - see [HTML5 Integration](./) for that path. It works on desktop and mobile browsers, and serves both [TrueX](./truex) and [IDVx](./idvx). The `` value in the VAST response tells them apart, the same as in the other integrations. > \[!NOTE] Client-side only > VPAID needs a client-side ad manager that runs the unit in the player. It does not apply to SSAI, where the ads are stitched into the stream on the server. For SSAI, use the [HTML5 Integration](./) path. ## What Does What | | Ad manager | Your app | | --- | --- | --- | | Requests the [VPAID tag](./vast-tags#vpaid-tag) | Yes | | | Loads and initializes the VPAID unit | Yes | | | Renders the choice card and the interactive ad | The VPAID unit, inside the ad manager's ad container | | | Handles the exit events - skips the ads in the pod, resumes the content | | Yes | Your app only reacts to the events below. If your ad manager already forwards VPAID events, you can usually do all of this in an event listener. ## Technical Requirements * **VAST and VPAID support.** Your ad manager must support VPAID 2.0+, as defined by the [IAB VAST and VPAID specifications](https://iabtechlab.com/standards/vast/). The unit is JavaScript-based and follows the VPAID 2.0 specification. * **The exit events** below, handled by your app. * **Flighting.** The tag must be in the right slot of each ad pod - see [Flighting](./vast-tags#flighting). If your player shows an ad countdown (for example "30 seconds until content resumes" or "Ad 1 of 4"), disable it or switch to generic text such as "Your video will resume shortly" while the unit is loaded. ## Exit Events The unit dispatches standard VPAID events, and one custom event. Your app needs the following to move the playback forward. | Event | Meaning | What your app does | | --- | --- | --- | | `AdFreePod` | TrueX only. The user fulfilled the time and interaction requirements of the ad. Custom event, not part of the VPAID specification | Do not present any more ads for the rest of the current ad pod. Keep the unit on screen - the user may keep interacting - until `AdStopped` | | `AdStopped` | The unit is done and can be removed | Remove the unit. If `AdFreePod` fired, skip the rest of the pod; otherwise play the next ad | | `AdSkipped` | The user opted out or the choice card timed out, or no ad was available for the user. A unit that identified itself as VPAID 1.0 in the handshake fires `AdStopped` instead | Play the next ad in the pod | | `AdError` | The unit failed | Play the next ad in the pod | `AdFreePod` fires when the user earns the credit, but the user stays in the ad until they select "Return to content", which appears after about 30 seconds and one interaction. Do not skip the ads before the unit stops. `AdFreePod` never fires for IDVx - after an IDVx ad, always continue with the next ad in the pod. ### When Custom Events Are Blocked Some ad frameworks and VPAID libraries do not pass custom events such as `AdFreePod` to your app. In that case, listen for the standard VPAID `AdInteraction` event instead, and check for an `Id` of `TrueXClosedAfterCredit`. It fires when the user selects "Return to content" after earning the credit - the same moment as `AdStopped` after `AdFreePod` - so that is when you skip the rest of the ad pod. For example, with Google IMA, which passes `AdInteraction` as an `INTERACTION` event: ```js adsManager.addEventListener( google.ima.AdEvent.Type.INTERACTION, (event) => { if (event.getAdData().id === 'TrueXClosedAfterCredit') { // The user earned the credit and returned to the content: // skip the rest of the ad pod adsManager.discardAdBreak(); } } ); ``` See the [VPAID Examples](./vpaid-examples) for a complete page. The unit fires these `AdInteraction` events, each identified by its `Id`: | `Id` | Fires when | | --- | --- | | `TrueXChoiceCardLoaded` | The choice card is shown to the user. Only fires if an ad is available | | `TrueXNoAdLoaded` | The choice card would have been shown, but no ad was available, so it is skipped | | `TrueXUserOptIn` | The user chooses to interact with the engagement ad | | `TrueXUserOptOut` | The user chooses to watch the normal commercial breaks instead | | `TrueXUserTimeOut` | The user does not choose in 15 seconds, and the choice card advances to the normal commercial breaks | | `TrueXCredit` | The user fulfilled the time and interaction requirements - the same moment as `AdFreePod`. The user is still in the ad | | `TrueXClosedAfterCredit` | The user selects "Return to content" after being credited - the same moment as `AdStopped` | | `TrueXClosedBeforeCredit` | The user closes the engagement ad before being credited | | `TrueXSkipCardLoaded` | The skip card ("Thank You" card) is shown to the user | Only `TrueXClosedAfterCredit` is needed to move the playback forward. The other events are for your own tracking. ### Other Events The unit also dispatches the other standard VPAID events: `AdLoaded`, `AdStarted`, `AdImpression`, `AdVideoStart`, `AdVideoFirstQuartile`, `AdVideoMidpoint`, `AdVideoThirdQuartile`, `AdVideoComplete`, `AdClickThru`, `AdInteraction`, and any event that follows a method call on the unit - for example `AdSizeChange` after `resizeAd()`, or `AdPaused` after `pauseAd()`. Your ad manager handles those. For what the user sees and how the ads work, see [TrueX](./truex) and [IDVx](./idvx). --- --- url: /ads/publisher/platforms/web/vpaid-examples.md --- # VPAID Examples A simple example that plays a TrueX ad through Google IMA (HTML5 SDK) with VPAID enabled, using the [VPAID tag](./vast-tags#vpaid-tag). > \[!WARNING] Live ads > The example requests a real ad when you select "Play ad". Choosing to interact, and staying with the ad, behaves like a real TrueX ad. ## Live Example Select "Play ad", choose "Yes" on the choice card, interact with the ad for about 30 seconds, and then select "Return to content". The log shows the events that Google IMA passes to your app. ## What the Example Shows * The choice card and the interactive ad are rendered by the VPAID unit, inside the ad container that IMA provides. * Google IMA does not pass custom events such as `AdFreePod` to your app. It passes the unit's `AdInteraction` events as an `INTERACTION` event, and the event's `id` is the `Id` - see [When Custom Events Are Blocked](./vpaid#when-custom-events-are-blocked). * The event you need is `TrueXClosedAfterCredit`, shown in bold in the log. It fires when the user selects "Return to content", which appears after about 30 seconds and one interaction. That is the point to skip the rest of the ad pod. * `CONTENT_RESUME_REQUESTED` arrives when the whole ad break ends. That is the point to resume the content. | VPAID event | Google IMA event | | --- | --- | | `AdLoaded` | `LOADED` | | `AdStarted`, `AdImpression` | `STARTED`, `IMPRESSION` | | `AdInteraction` (with an `Id`) | `INTERACTION` (`getAdData().id`) | | `AdStopped` | `COMPLETE` (the ad ends), then `CONTENT_RESUME_REQUESTED` (the ad break ends) | | `AdSkipped` | `SKIPPED` | | `AdError` | `AD_ERROR` | ## Setup Enable VPAID in the IMA settings, and request the tag. IMA runs the VPAID unit for you: ```js google.ima.settings.setVpaidMode( google.ima.ImaSdkSettings.VpaidMode.ENABLED ); const displayContainer = new google.ima.AdDisplayContainer(adContainer, videoElement); displayContainer.initialize(); const adsLoader = new google.ima.AdsLoader(displayContainer); const request = new google.ima.AdsRequest(); request.adTagUrl = 'https://get.truex.com/{placement_hash}/vast'; adsLoader.requestAds(request); ``` ## Skipping the Ad Break Skip the ad break when the user returns to the content after earning the credit, and resume the content when the ad break ends: ```js adsManager.addEventListener( google.ima.AdEvent.Type.INTERACTION, (event) => { if (event.getAdData().id === 'TrueXClosedAfterCredit') { // Skip the rest of the ad pod adsManager.discardAdBreak(); } } ); adsManager.addEventListener( google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED, () => { // The ad break is over videoElement.play(); } ); ``` > \[!NOTE] discardAdBreak > `adsManager.discardAdBreak()` discards only the current ad break, and keeps the later ones. It does nothing unless the ad request returned a VMAP or playlist response, such as an ad pod. With a single ad, as in this example, the content resumes when the ad ends. ## Full Listing The page that runs in the live example above: ```html VPAID demo player
``` --- --- url: /ads/publisher/platforms/roku/truex.md --- # TrueX ### Product Flow **TrueX** presents users with an **interactive choice card** where they choose between engaging with branded content or watching standard video ads. When users opt in and complete the interactive experience, they earn an **ad credit that skips all remaining ads** in the pod, creating a win-win: viewers get ad-free content, and advertisers get highly engaged audiences. **Key features:** * **Opt-in via choice card** - Users actively choose to engage * **Engagement requirements** - Users must spend 30 seconds and interact at least once to earn credit * **Skips entire ad break** - Successful engagement bypasses all remaining ads in the pod Upon completion, the renderer signals a `adFreePod` event, indicating that remaining linear ads should be skipped. There are two distinct product flows supported by `TruexAdRenderer`: Sponsored Stream (full-stream ad-replacement) and Sponsored Ad Break (mid-roll ad-replacement). In a Sponsored Ad Break flow, once the user hits a mid-roll break with a **TrueX** tag flighted, they will be shown a "choice-card" offering them the choice between watching a normal set of video ads or a fully interactive **TrueX** ad: ![choice card](/assets/images/choice_card.png) ***Fig. A** example **TrueX** mid-roll choice card* If the user opts for a normal ad break, or if the user does not make a selection before the countdown timer expires, the **TrueX** UI will close and playback of normal video ads can continue as usual. If the user opts to interact with **TrueX**, an interactive ad unit will be shown to the user: ![ad](/assets/images/ad.png) ***Fig. B** example **TrueX** interactive ad unit* The requirement for the user to "complete" this ad is for them to spend at least the allotted time on the unit and for at least one interaction (e.g. navigating anywhere through the ad). ![true attention timer example](/assets/images/true-attention-timer-example.png) ***Fig. C** example **TrueX** attention timer* Once the user fulfills both requirements, a "Watch Your Show" button will appear in the bottom right, which the user can select to exit the **TrueX** ad. Having completed a **TrueX** ad, the user will be returned directly to content, skipping the remaining ads in the current ad pod. The Sponsored Stream flow is quite similar. In this scenario, a user will be shown a choice-card in the pre-roll: ![choice card](/assets/images/choice_card.png) ***Fig. D** example **TrueX** pre-roll choice card (full-stream replacement)* Similarly, if the user opts-in and completes the **TrueX** ad, they will be skipped over the remainder of the pre-roll ad break. However, every subsequent mid-roll break in the current stream will also be skipped over. In this case instead of the regular pod of video ads, the user will be shown a "hero card" (also known as a "skip card"): ![skip card](/assets/images/skip_card.png) ***Fig. E** example **TrueX** mid-roll skip card* This messaging will be displayed to the user for several seconds, after which they will be returned directly to content. --- --- url: /ads/publisher/platforms/roku/idvx.md --- # Interactive Video (IDVx) ### How Interactive Video (IDVx) Works **Interactive Video (IDVx)** delivers a new industry standard in video engagement across all devices and platforms. The product goes beyond hovers and inadvertent clicks, to deliver real interaction, by real humans, in premium environments. Built on the same core interactive foundation as TrueX, Interactive Video removes the opt-in, interaction, and time spent requirements, in order to capitalize on the scale of a Publisher's inventory. Infillion pays Publishers a set CPM for the IDVx ads the Publisher renders in their ad pods. **Key features:** * **Automatic start** - Begins playing without opt-in; there is no choice card * **Interactive throughout** - Users can interact with ad content during playback, but no interaction is required * **Capped at 30 seconds** - Unlike the unlimited time a user can spend in a TrueX ad * **Plays inline** - Completes and continues to the next ad in the pod; it does not skip or suppress the Publisher's ad pod ![Interactive Video ad pod flow](/assets/images/image5.jpg) ***Fig. A** example of an IDVx ad playing inline within a regular ad pod* #### User Flow 1. A viewer is streaming video content and reaches a commercial break 2. The content is paused to load the ad pod 3. The IDVx ad is loaded like any other ad, and is one of many in the Publisher's ad pod 4. When the user comes across the IDVx ad, they can interact with it if they choose 5. IDVx ads are 30 seconds long, so when those 30 seconds are up the ad ends 6. The video player then loads either another ad or the video content #### Supported Platforms IDVx ads are available on the following platforms: * [Vega (FireTV)](/platforms/vega/) * [Web/HTML](/platforms/web/) - desktop browsers and HTML5-based CTV apps * [Roku](/platforms/roku/) * [Android](/platforms/android/) - TV & mobile * [iOS](/platforms/ios/) * [tvOS](/platforms/tvos/) - coming soon ## Flighting the IDVx Placement Tag The IDVx placement tag is a 1 stage tag and can be flighted in every ad slot of a pod (except any reserved for TrueX) according to the priority the Publisher wants to give Infillion in filling that slot. For example the Publisher may wish to put the IDVx tag behind direct sold campaign tags, but ahead of programmatic ad tags. Or the Publisher may wish to give the IDVx tag the first position in every last slot of every pod. :::steps 1. The Publisher's AdOps team schedules the IDVx placement tag in any priority they wish throughout their network in their ad server 2. If the Publisher already has the TrueX SDK installed, they need to make a few changes to the integration code ([see below](#changes-existing-truex-publishers-need-to-make-to-support-idvx-ads)) 3. When the ad pod is triggered by the user, the IDVx ad tag will request an ad from the Infillion ad server (we support both CSAI and SSAI) 4. If a match is found for the user, the Infillion ad server will send an IDVx ad response that will trigger the IDVx user flow detailed above 5. If no IDVx ad is matched for the user by the Infillion ad server, an empty VAST response will be passed back, triggering whatever is scheduled next - either another video ad or the content ::: ## Changes existing TrueX Publishers need to make to support IDVx ads If your Roku app already renders TrueX ads: 1. Allow the renderer to run for ads with `` `IDVx` in any pod position. The "first ad in the pod" rule applies to TrueX only. 2. Initialize the renderer with the `adParameters` JSON from the VAST response - see [SDK Integration](./sdk-integration#init). 3. After an IDVx ad finishes, always continue with the next ad in the pod. IDVx never fires `adFreePod` - see [Integration Flow](./integration-flow#handling-terminal-events). ## Creative Example This animated gif illustrates the interactivity of an IDVx ad creative. ![Interactive Video creative example](/assets/images/image3.gif) > \[!TIP] > The [Infillion Engagement Showcase](https://engagements.infillion.com/showcase/) shows working examples of TrueX and IDVx ads on all supported devices and platforms. If the ad experience does not load right away in the demo environment, refresh the page and it should load properly. --- --- url: /ads/publisher/platforms/roku.md --- # Roku Integration The TrueX Ad Renderer (TAR) for Roku renders Infillion interactive ads - [TrueX](./truex) and [IDVx](./idvx) - natively inside Roku apps that play long-form video with ad breaks, known as FEP (Full Episode Player) apps. TAR is a Roku component library, `TruexAdRenderer`, that your app loads and controls through `action` and `event` fields. The renderer requests the ad from Infillion, builds the native UI (the TrueX choice card and the interactive ad unit), and tells your app when action is required. > \[!TIP] Reference Apps > For complete working examples, see the [Examples](./examples) page. ## Supported Devices The TAR component library supports Roku devices that run SceneGraph apps (BrightScript). ## TrueX and IDVx Both ad products are requested with a VAST tag in one of two formats, generic or companion - see [VAST Tag Types](./vast-tags). The same `TruexAdRenderer` renders both, and your app tells them apart by the VAST `` value: | | [TrueX](./truex) | [IDVx](./idvx) | | --- | --- | --- | | `` | `trueX` | `IDVx` | | Entry | Opt-in through a choice card | Starts automatically | | Pod position | First ad in the pod only | Any slot | | Reward | `adFreePod` - the remaining ads in the pod are skipped | None - the pod continues | ## What You'll Need * A Roku app with a video player and an ad integration (RAF client-side ads, Google IMA DAI, another SSAI vendor, or your own ad layer) * The `TruexAdRenderer` component library - see [SDK Integration](./sdk-integration#loading-the-library) * A full-screen SceneGraph node above your video player where the renderer can show the interactive ad * A test VAST tag from your Infillion contact - see [Integration Flow](./integration-flow) ## Next Steps * [Integration Flow](./integration-flow) - what Infillion provides, and what your app has to build * [SDK Integration](./sdk-integration) - loading the library and the `init`, `start`, and `stop` actions * [Events](./events) - every event the renderer emits * [Examples](./examples) - runnable reference apps for RAF CSAI, CSAI without RAF, and Google IMA DAI --- --- url: /ads/publisher/platforms/roku/vast-tags.md --- # VAST Tag Types Infillion uses two types of VAST tags for both TrueX and Interactive Video products. The tags differ in how the `adParameters` JSON payload is embedded in the VAST XML. ### "Generic" Tag The `adParameters` are embedded directly as a JSON string within the `` node: **TrueX "Generic" Tag:** ``` https://get.truex.com/{placement_hash}/vast/generic?{ad_request_parameters} ``` **Interactive Video "Generic" Tag:** ``` https://get.truex.com/{placement_hash}/vast/idvx/generic?{ad_request_parameters} ``` **VAST Structure:** ```xml trueX 00:00:30 ``` ### "Companion" Tag The `adParameters` are embedded in a companion ad's `` node as base64-encoded json string: **TrueX "Companion" Tag:** ``` https://get.truex.com/{placement_hash}/vast/companion?{ad_request_parameters} ``` **IDVx "Companion" Tag:** ``` https://get.truex.com/{placement_hash}/vast/idvx/companion?{ad_request_parameters} ``` **VAST Structure:** ```xml trueX 00:00:30 ``` ### Differentiating TrueX and IDVx The `` node value identifies the ad type: * **TrueX ads**: `trueX` * **IDVx ads**: `IDVx` Both tag types (Generic and Companion) work with both TrueX and IDVx products. --- --- url: /ads/publisher/platforms/roku/integration-flow.md --- # Integration Flow A Roku 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. :::steps 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](./vast-tags). 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 can be placed in any slot. 3. The publisher integrates the SDK Your app [resolves `adParameters`](#resolving-adparameters) from the ad, [starts the renderer](#starting-the-renderer) when the ad plays, and [handles the terminal events](#handling-terminal-events). ::: ## Resolving `adParameters` The renderer is initialized with an `adParameters` associative array. Where that JSON comes from depends on the VAST tag format: | Tag format | Where `adParameters` is in the VAST | RAF | Custom VAST parsing | | --- | --- | --- | --- | | Generic | `` as a JSON string | `ad.adParameters` | Text content of `` | | Companion | `` as a base64 JSON `data:` URL | `ad.companionAds`, the entry whose `provider` is `truex` | Text content of ``, base64-decoded after the comma | First, identify the ad by its `` value, compared case-insensitively. `trueX` is a TrueX ad and `IDVx` is an IDVx 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. To support both formats, resolve the payload in this order: 1. Use the `` JSON if the ad has it. 2. Otherwise, look for a companion with `apiFramework="truex"` and decode its 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 decoding, adapted from the [CSAI example](./examples#csai-without-raf): ```brightscript function resolveAdParameters(ad as Object) as Object json = invalid if ad.adParameters <> invalid and ad.adParameters <> "" then json = ad.adParameters else if ad.companionAds <> invalid then for each companion in ad.companionAds if LCase(companion.provider) = "truex" and companion.url <> invalid then ' The ad server wraps the base64 payload across lines, so line breaks are removed before decoding. base64 = companion.url.Split("data:application/json;base64,")[1] if base64 <> invalid then buffer = CreateObject("roByteArray") buffer.FromBase64String(base64.Replace(Chr(10), "")) json = buffer.ToAsciiString() end if exit for end if end for end if if json = invalid then return invalid end if return ParseJson(json) end function ``` The result of `ParseJson` is passed to the renderer as is. Do not read or change its fields. ## 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: * RAF and custom ad layers: do not play the placeholder; the renderer takes its place in the pod. * SSAI: the placeholder is stitched into the stream, so seek over it (for example, to the end of the placeholder ad) before or after the renderer runs. 3. Create a `TruexAdRenderer` with `createChild`, observe its `event` field, and send the `init` action with the `adParameters`. If the `adParameters` are missing or invalid, there will be no events: continue the pod. 4. Send the `start` action when your app is ready to show the ad. 5. Wait for a terminal event. While you wait, keep track of whether `adFreePod` has fired. The actions are described in [SDK Integration](./sdk-integration). ## 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 result | What to do | RAF CSAI | Google IMA DAI (SSAI) | Custom ad layer | | --- | --- | --- | --- | --- | | `adCompleted` after `adFreePod` | Skip all remaining ads in the pod and continue the content | Do not call `showAds` for the pod, resume the content | Seek past the end of the current ad break | End the pod and restore the content | | `adCompleted` without `adFreePod` | Skip the Infillion placeholder ad and play the next ad in the pod | Play the remaining ads of the pod | Seek to just before the placeholder's end, so the next stitched ad plays | Play the next ad in the pod | | `adError` or `noAdsAvailable` | Same as `adCompleted` without `adFreePod` | Same | Same | Same | | `userCancelStream` | The user closed the Infillion ad because they want to leave - exit the playback screen | Close the player | Close the player | Close the player | `adCompleted` without `adFreePod` covers a TrueX user who opted out or let the choice card time out, and it is the normal outcome of every IDVx ad, because IDVx never fires `adFreePod`. > \[!IMPORTANT] > Do not resume or seek when `adFreePod` fires. Remember that it fired, and act only on the terminal event that follows. After any terminal event, hide the renderer, stop observing its `event` field, remove the `TruexAdRenderer` node from its parent, and drop all references to it. The full list of terminal events is on the [Events](./events#terminal-events) page. --- --- url: /ads/publisher/platforms/roku/sdk-integration.md --- # SDK Integration ## Loading the Library The TrueX ad renderer and its rendering logic are distributed as a Roku component library. Add a reference to it, for example in your app's main scene: ```xml ``` The library loads asynchronously. Observe its `loadStatus` field and create the renderer only when it is `ready`: ```brightscript sub init() m.tarLibrary = m.top.findNode("TruexAdRendererLib") m.tarLibrary.observeFieldScoped("loadStatus", "onTruexLibraryLoadStatusChanged") end sub sub onTruexLibraryLoadStatusChanged(event as Object) if m.tarLibrary.loadStatus = "ready" then ' It is safe to create TruexLibrary:TruexAdRenderer now else if m.tarLibrary.loadStatus = "failed" then ' Play your regular ads - there will be no TrueX or IDVx ads in this session end if end sub ``` > \[!NOTE] Library version > We recommend the URL above, because it distributes performance and functionality updates without a new app release. A major/minor/build-specific library URL is available on request if you need to link to a specific build. ## Renderer Container The renderer adds its UI as a child of the `TruexAdRenderer` node, so create the renderer in a full-screen node above your video player. Hide as much other UI as possible while the ad is showing, including player controls, status bars, and keyboards. ## Quick Example ```brightscript sub startTruexAd(adParameters as Object, slotType as String) m.tar = m.top.createChild("TruexLibrary:TruexAdRenderer") m.tar.observeFieldScoped("event", "onTruexEvent") m.tar.setFocus(true) m.truexAdFreePod = false m.tar.action = { type: "init", adParameters: adParameters, slotType: UCase(slotType), supportsUserCancelStream: true } m.tar.action = { type: "start" } end sub sub onTruexEvent(msg as Object) event = msg.getData() if event.type = "adFreePod" then m.truexAdFreePod = true else if event.type = "adCompleted" or event.type = "noAdsAvailable" then disposeTruexRenderer() continuePlayback(m.truexAdFreePod) else if event.type = "adError" then disposeTruexRenderer() continuePlayback(false) else if event.type = "userCancelStream" then disposeTruexRenderer() exitPlayback() end if end sub sub disposeTruexRenderer() m.tar.visible = false m.tar.setFocus(false) m.tar.unobserveFieldScoped("event") m.tar.getParent().removeChild(m.tar) m.tar = invalid end sub ``` `continuePlayback` and `exitPlayback` are your app's code - see [Handling Terminal Events](./integration-flow#handling-terminal-events). The `init` and `start` actions are described below, and the events on the [Events](./events) page. ## `TruexAdRenderer` Actions You control the renderer by assigning an associative array with a `type` to its `action` field. The renderer reports back through its `event` field, which is declared with `alwaysNotify`. ### `init` ```brightscript m.tar.action = { type: "init", adParameters: adParameters, slotType: "PREROLL", supportsUserCancelStream: true, logLevel: 1, channelWidth: 1920, channelHeight: 1080 } ``` Initializes the renderer. The renderer reads the `adParameters`, requests the ad from Infillion, and prepares the UI. `init` can only be sent once per renderer instance: create a new `TruexAdRenderer` for every ad. You can initialize the renderer early - a few seconds before the pod starts - to give it more time to finish the ad request. It fires [`adFetchCompleted`](./events#adfetchcompleted) when the request is done. Use that event for a loading indicator, or for a timeout before you call `start`. The parameters are: | Parameter | Required | Description | | --- | --- | --- | | `adParameters` | Yes | The ad parameters associative array resolved from the VAST - see [Resolving `adParameters`](./integration-flow#resolving-adparameters) | | `slotType` | Yes | The type of the current ad pod: `PREROLL` or `MIDROLL` | | `supportsUserCancelStream` | No | Set to `true` to enable the [`userCancelStream`](./events#usercancelstream) event. Without it, the renderer fires `adCompleted` instead | | `logLevel` | No | Verbosity of TrueX logging, from `0` (mute) to `5` (verbose). Defaults to `5` | | `channelWidth` | No | Width in pixels of your app's UI. Defaults to `1920` | | `channelHeight` | No | Height in pixels of your app's UI. Defaults to `1080` | ### `start` ```brightscript m.tar.action = { type: "start" } ``` Tells the renderer that your app is ready to show the ad. It can be sent any time after `init`. If the ad request is still running, the renderer waits for it to finish. After `start`, the renderer adds its UI to the `TruexAdRenderer` node and fires [`adStarted`](./events#adstarted). Then wait for a [terminal event](./events#terminal-events). ### `stop` ```brightscript m.tar.action = { type: "stop" } ``` Stops the renderer and dismisses the ad that is showing. Use it when your app has to end the ad - for example, when a timeout is reached or the app is closing. Remove the renderer node afterwards. ### `get_tar_info` ```brightscript m.tar.action = { type: "get_tar_info" } ``` Returns runtime metadata about the renderer. The result arrives on the `event` field as `{ type: "tarInfo", fullVersionString: "" }`. ### `fireDebugPixel` ```brightscript m.tar.action = { type: "fireDebugPixel", name: "my-debug-name", value: "optional value" } ``` Fires a TrueX `t.gif` debug pixel with the given `name` (required) and `value` (optional). Use it only when your Infillion contact asks you to. ## Handling Events Observe the `event` field of the renderer, and read the event with `getData()`: ```brightscript sub onTruexEvent(msg as Object) event = msg.getData() ? "TrueX event: "; event.type end sub ``` See [Events](./events) for every event type and its parameters. --- --- url: /ads/publisher/platforms/roku/events.md --- # Events `TruexAdRenderer` tells your app about the ad lifecycle through its `event` field. Each event is an associative array whose `type` identifies it. [`adCompleted`](#adcompleted), [`adError`](#aderror), and [`noAdsAvailable`](#noadsavailable) must be handled, as well as [`adFreePod`](#adfreepod); the rest are optional. | Event | Required | Terminal | | --- | --- | --- | | [`adFetchCompleted`](#adfetchcompleted) | | | | [`adStarted`](#adstarted) | | | | [`adCompleted`](#adcompleted) | Yes | Yes | | [`adError`](#aderror) | Yes | Yes | | [`noAdsAvailable`](#noadsavailable) | Yes | Yes | | [`adFreePod`](#adfreepod) | Yes | | | [`userCancelStream`](#usercancelstream) | | Yes | | [`adsAvailable`](#adsavailable) | | | | [`optIn`](#optin) | | | | [`optOut`](#optout) | | | | [`skipCardShown`](#skipcardshown) | | | | [`userCancel`](#usercancel) | | | | [`videoEvent`](#videoevent) | | | All events are read in the handler of the `event` field: ```brightscript function handleTarEvent(evt as Object) as Void adEvent = evt.getData() ' adEvent.type is the event name end function ``` ## Event Flow A typical TrueX ad where the user opts in and earns the reward: ``` ◇ adFetchCompleted │ ↓ ◇ adStarted │ ↓ ◇ optIn │ ↓ ◆ [User interacts with the ad] │ ↓ ◇ adFreePod │ ↓ ◇ adCompleted ``` An IDVx ad has no choice card and no reward, so it goes from `adStarted` straight to `adCompleted`, without `optIn`, `optOut`, or `adFreePod`. ## Terminal Events A terminal event means the renderer is done, and your app must continue playback - see [Handling Terminal Events](./integration-flow#handling-terminal-events) for what to do in each case: * [`adCompleted`](#adcompleted) - the ad is finished * [`adError`](#aderror) - the renderer hit an unrecoverable error * [`noAdsAvailable`](#noadsavailable) - there is no ad for this user * [`userCancelStream`](#usercancelstream) - the user wants to exit the stream After a terminal event, remove the `TruexAdRenderer` component from the Scene Graph. ## Main Flow Events These events drive the integration and may require action from your app. ### `adFetchCompleted` ```brightscript ' adEvent : { ' type : "adFetchCompleted" ' } ``` Optional. Fires after [`init`](./sdk-integration#init), when the ad request is done and the ad is ready to be shown. You can use it for a loading screen or a timeout. For example, send `init` for a preroll, show your loading indicator, and send `start` when this event arrives - or, if your timeout is reached first, remove the renderer and continue with the regular ads. For a midroll, send `init` well before the slot; if the event arrived by the time the slot is reached, send `start` to show the ad immediately. ### `adStarted` ```brightscript ' adEvent : { ' type : "adStarted", ' campaignName : ' } ``` Optional. Fires after [`start`](./sdk-integration#start), when the renderer's UI is ready and added to the component hierarchy. You can use it for your own timeout: if `adStarted` does not fire in time, continue with the regular ads. The parameters are: * `campaignName`: the name of the ad campaign available to the user ### `adCompleted` ```brightscript ' adEvent : { ' type : "adCompleted", ' timeSpent : ' } ``` Required. **Terminal event.** Fires when the ad is finished, for example when: * the TrueX user chooses the normal video ads, or the choice card countdown runs out * the TrueX user completes the ad and exits it * a TrueX skip card has been shown for its duration * an IDVx ad ends The parameters are: * `timeSpent`: time in seconds the user spent in the ad If `adFreePod` fired before, skip all remaining ads in the pod. Otherwise, continue with the next ad. ### `adError` ```brightscript ' adEvent : { ' type : "adError", ' errorMessage : ' } ``` Required. **Terminal event.** Fires when the renderer hits an unrecoverable error. Handle it like `adCompleted` without `adFreePod`. The parameters are: * `errorMessage`: a description of the error ### `noAdsAvailable` ```brightscript ' adEvent : { ' type : "noAdsAvailable" ' } ``` Required. **Terminal event.** Fires when there is no ad available for this user. Handle it like `adCompleted` without `adFreePod`. ### `adFreePod` ```brightscript ' adEvent : { ' type : "adFreePod" ' } ``` Required. Fires when a TrueX user has earned the reward. **Do not resume or seek yet** - remember that it fired, and when the terminal `adCompleted` arrives, skip all remaining ads in the pod. IDVx ads never fire this event. ### `userCancelStream` ```brightscript ' adEvent : { ' type : "userCancelStream" ' } ``` **Terminal event.** Enabled only when `supportsUserCancelStream` is `true` in the [`init`](./sdk-integration#init) action. Fires when the user wants to exit the stream entirely. Treat it like any other "exit" action from the stream - usually, return the user to the episode or series page. If it is not enabled, the renderer fires [`adCompleted`](#adcompleted) instead. ## Informative Events These events are used mostly for tracking. No action is generally required. ### `adsAvailable` ```brightscript ' adEvent : { ' type : "adsAvailable" ' } ``` Fires after a successful ad request, together with `adFetchCompleted`, to tell that ad inventory is available. It is the opposite of [`noAdsAvailable`](#noadsavailable), and helps you decide when a pre-loaded renderer can be started. ### `optIn` ```brightscript ' adEvent : { ' type : "optIn" ' } ``` Fires when the user chooses to interact with the TrueX ad. It can fire more than once if the user opts in, backs out, and opts in again. IDVx ads never fire it. ### `optOut` ```brightscript ' adEvent : { ' type : "optOut", ' userInitiated : ' } ``` Fires when the user chooses the normal video ad experience. IDVx ads never fire it. The parameters are: * `userInitiated`: `true` if the user chose it, `false` if the choice card countdown ran out ### `skipCardShown` ```brightscript ' adEvent : { ' type : "skipCardShown" ' } ``` Fires when a TrueX "skip card" is shown to the user, as a result of completing a TrueX Sponsored Stream ad in an earlier preroll. ### `userCancel` ```brightscript ' adEvent : { ' type : "userCancel" ' } ``` Fires when the user opted in and then backs out of the TrueX ad, and goes back to the choice card (with the countdown restarted). After a `userCancel`, the user can opt in again, so more `optIn` or `optOut` events can follow. ### `videoEvent` ```brightscript ' adEvent : { ' type : "videoEvent", ' subType : "", ' videoName : "", ' url : "" ' } ``` Fires when something noteworthy happens in a video inside the ad. The `subType` is one of: * `started`: a video has started * `firstQuartile`: a video has reached its first quartile * `secondQuartile`: a video has reached its second quartile * `thirdQuartile`: a video has reached its third quartile * `completed`: a video has reached completion The parameters are: * `subType`: the type of the video event * `videoName`: the name of the video, if available * `url`: the source URL of the video, if available --- --- url: /ads/publisher/platforms/roku/examples.md --- # Examples Three Roku reference apps show a complete integration of TrueX and IDVx ads, one for each way ads are inserted: | Example | Ad source | Ad break behavior | | --- | --- | --- | | [CSAI without RAF](#csai-without-raf) | A VAST/VMAP response parsed by the app | The app schedules and plays the ad pod itself | | [RAF CSAI](#raf-csai) | A VMAP parsed by the Roku Advertising Framework (RAF) | RAF parses the response and plays the regular ads | | [Google IMA DAI](#google-ima-dai) | A Google DAI stream | Google DAI returns one stream with stitched ad breaks | Each example has its own player, ad, and `TruexAdRenderer` code on purpose, so you can pick the one closest to your setup and read it from top to bottom. All three load the library the same way - see [SDK Integration](./sdk-integration#loading-the-library). ## Running the Apps Requirements: * A Roku device in developer mode * The app source from the repository below, sideloaded to the device > \[!NOTE] Test IP parameter > The TrueX ad server serves ads in the US and Canada only. If your team or CI runs outside those regions, ask your Infillion contact for the test `ip` parameter to add to the sample VAST tags. Do not use it in production. ## Shared Rules All three examples follow the same rules, implemented as small functions you can copy: * An ad is an Infillion ad if its `` is `trueX` or `IDVx`, compared case-insensitively. * TrueX is interactive only as the first ad in the pod; at a later position it plays as a regular linear ad. IDVx is interactive in any position. * `adParameters` comes from the ad (see [Resolving `adParameters`](./integration-flow#resolving-adparameters)). If it is missing or invalid, the renderer is not started and the pod continues. * Only a TrueX ad that fired `adFreePod` skips the rest of the pod after `adCompleted`. * `userCancelStream` closes the player screen. ## CSAI without RAF Repository: [truex-roku-csai-reference-app][csai] The app parses the VAST itself and plays the ad pod with its own player, without the Roku Advertising Framework. 1. **Load the library** - `main-scene.xml` declares the `ComponentLibrary` and `main-scene.brs` waits for `loadStatus` to be `ready`. 2. **Detect the ad** - `playback-screen.brs` checks the `` value of each ad in the pod. 3. **Resolve `adParameters`** - the generic `` JSON, or the base64 companion payload. 4. **Start the renderer** - it creates `TruexLibrary:TruexAdRenderer`, sends `init` and `start`, and handles the events in `onTruexEvent`. 5. **Continue the pod** - after the terminal event, the app discards the rest of the pod after a TrueX `adFreePod`, or starts the next ad in the pod. ## RAF CSAI Repository: [truex-roku-raf-csai-reference-app][raf] RAF parses the ad response, finds the ads, and plays the regular ads. The app only takes over for the Infillion ad. 1. **Set up RAF** - `PlaybackTask.brs` gives RAF the ad URL (a VMAP with TrueX ads in a preroll and a midroll pod). 2. **Detect the ad** - `handleAdPod()` reads the pod with `Roku_Ads().getAds()` and classifies each ad. 3. **Play the Infillion ad** - `playTrueXAd()` creates the renderer in the ad container passed by the flow, resolves `adParameters` from the ad's `adParameters` or `companionAds`, and waits for the terminal event. 4. **Play the rest of the pod** - if the user did not earn the reward, `Roku_Ads().showAds()` plays the remaining ads. RAF owns the thread until the ads finish, so the app restores the content position afterwards. ## Google IMA DAI Repository: [truex-roku-google-ad-manager-reference-app][ima] The Google IMA SDK for Roku plays a DAI stream with the ads stitched into it. The integration is in `ImaSdkTask.brs` and `ContentFlow.brs`, and the code is marked with numbered comments that match the steps in the repository README: 1. **Keep track of the current ad break** - save the break in the IMA `adBreakStarted` callback. 2. **Look for the Infillion companion** - in `onStreamStarted`, inspect the ad's companions for `apiFramework` `truex`. 3. **Parse `adParameters`** - decode the base64 companion payload into JSON. 4. **Prepare to enter the ad** - pause the stream and seek over the placeholder ad. 5. **Initialize and start the renderer** - send `init`, wait for `adFetchCompleted`, and send `start`. 6. **Respond to `adFreePod`** - seek the stream over the current ad break. 7. **Respond to the terminal events** - resume playback after `adCompleted`, `adError`, or `noAdsAvailable`. 8. **Respond to `userCancelStream`** - leave the stream. [csai]: https://github.com/socialvibe/truex-roku-csai-reference-app [raf]: https://github.com/socialvibe/truex-roku-raf-csai-reference-app [ima]: https://github.com/socialvibe/truex-roku-google-ad-manager-reference-app --- --- url: /ads/publisher/platforms/android/truex.md --- # TrueX ### Product Flow **TrueX** presents users with an **interactive choice card** where they choose between engaging with branded content or watching standard video ads. When users opt in and complete the interactive experience, they earn an **ad credit that skips all remaining ads** in the pod, creating a win-win: viewers get ad-free content, and advertisers get highly engaged audiences. **Key features:** * **Opt-in via choice card** - Users actively choose to engage * **Engagement requirements** - Users must spend 30 seconds and interact at least once to earn credit * **Skips entire ad break** - Successful engagement bypasses all remaining ads in the pod Upon completion, the renderer signals a `adFreePod` event, indicating that remaining linear ads should be skipped. There are two distinct product flows supported by `TruexAdRenderer`: Sponsored Stream (full-stream ad-replacement) and Sponsored Ad Break (mid-roll ad-replacement). In a Sponsored Ad Break flow, once the user hits a mid-roll break with a **TrueX** tag flighted, they will be shown a "choice-card" offering them the choice between watching a normal set of video ads or a fully interactive **TrueX** ad: ![choice card](/assets/images/choice_card.png) ***Fig. A** example **TrueX** mid-roll choice card* If the user opts for a normal ad break, or if the user does not make a selection before the countdown timer expires, the **TrueX** UI will close and playback of normal video ads can continue as usual. If the user opts to interact with **TrueX**, an interactive ad unit will be shown to the user: ![ad](/assets/images/ad.png) ***Fig. B** example **TrueX** interactive ad unit* The requirement for the user to "complete" this ad is for them to spend at least the allotted time on the unit and for at least one interaction (e.g. navigating anywhere through the ad). ![true attention timer example](/assets/images/true-attention-timer-example.png) ***Fig. C** example **TrueX** attention timer* Once the user fulfills both requirements, a "Watch Your Show" button will appear in the bottom right, which the user can select to exit the **TrueX** ad. Having completed a **TrueX** ad, the user will be returned directly to content, skipping the remaining ads in the current ad pod. The Sponsored Stream flow is quite similar. In this scenario, a user will be shown a choice-card in the pre-roll: ![choice card](/assets/images/choice_card.png) ***Fig. D** example **TrueX** pre-roll choice card (full-stream replacement)* Similarly, if the user opts-in and completes the **TrueX** ad, they will be skipped over the remainder of the pre-roll ad break. However, every subsequent mid-roll break in the current stream will also be skipped over. In this case instead of the regular pod of video ads, the user will be shown a "hero card" (also known as a "skip card"): ![skip card](/assets/images/skip_card.png) ***Fig. E** example **TrueX** mid-roll skip card* This messaging will be displayed to the user for several seconds, after which they will be returned directly to content. --- --- url: /ads/publisher/platforms/android/idvx.md --- # Interactive Video (IDVx) ### How Interactive Video (IDVx) Works **Interactive Video (IDVx)** delivers a new industry standard in video engagement across all devices and platforms. The product goes beyond hovers and inadvertent clicks, to deliver real interaction, by real humans, in premium environments. Built on the same core interactive foundation as TrueX, Interactive Video removes the opt-in, interaction, and time spent requirements, in order to capitalize on the scale of a Publisher's inventory. Infillion pays Publishers a set CPM for the IDVx ads the Publisher renders in their ad pods. **Key features:** * **Automatic start** - Begins playing without opt-in; there is no choice card * **Interactive throughout** - Users can interact with ad content during playback, but no interaction is required * **Capped at 30 seconds** - Unlike the unlimited time a user can spend in a TrueX ad * **Plays inline** - Completes and continues to the next ad in the pod; it does not skip or suppress the Publisher's ad pod ![Interactive Video ad pod flow](/assets/images/image5.jpg) ***Fig. A** example of an IDVx ad playing inline within a regular ad pod* #### User Flow 1. A viewer is streaming video content and reaches a commercial break 2. The content is paused to load the ad pod 3. The IDVx ad is loaded like any other ad, and is one of many in the Publisher's ad pod 4. When the user comes across the IDVx ad, they can interact with it if they choose 5. IDVx ads are 30 seconds long, so when those 30 seconds are up the ad ends 6. The video player then loads either another ad or the video content #### Supported Platforms IDVx ads are available on the following platforms: * [Vega (FireTV)](/platforms/vega/) * [Web/HTML](/platforms/web/) - desktop browsers and HTML5-based CTV apps * [Roku](/platforms/roku/) * [Android](/platforms/android/) - TV & mobile * [iOS](/platforms/ios/) * [tvOS](/platforms/tvos/) - coming soon ## Flighting the IDVx Placement Tag The IDVx placement tag is a 1 stage tag and can be flighted in every ad slot of a pod (except any reserved for TrueX) according to the priority the Publisher wants to give Infillion in filling that slot. For example the Publisher may wish to put the IDVx tag behind direct sold campaign tags, but ahead of programmatic ad tags. Or the Publisher may wish to give the IDVx tag the first position in every last slot of every pod. :::steps 1. The Publisher's AdOps team schedules the IDVx placement tag in any priority they wish throughout their network in their ad server 2. If the Publisher already has the TrueX SDK installed, they need to make a few changes to the integration code ([see below](#changes-existing-truex-publishers-need-to-make-to-support-idvx-ads)) 3. When the ad pod is triggered by the user, the IDVx ad tag will request an ad from the Infillion ad server (we support both CSAI and SSAI) 4. If a match is found for the user, the Infillion ad server will send an IDVx ad response that will trigger the IDVx user flow detailed above 5. If no IDVx ad is matched for the user by the Infillion ad server, an empty VAST response will be passed back, triggering whatever is scheduled next - either another video ad or the content ::: ## Changes existing TrueX Publishers need to make to support IDVx ads If your Android app already renders TrueX ads: 1. Allow the renderer to run for ads with `` `IDVx` in any pod position. The "first ad in the pod" rule applies to TrueX only. 2. Initialize the renderer with the `adParameters` JSON from the VAST response - see [SDK Integration](./sdk-integration#init). 3. After an IDVx ad finishes, always continue with the next ad in the pod. IDVx never fires `AD_FREE_POD` - see [Integration Flow](./integration-flow#handling-terminal-events). ## Creative Example This animated gif illustrates the interactivity of an IDVx ad creative. ![Interactive Video creative example](/assets/images/image3.gif) > \[!TIP] > The [Infillion Engagement Showcase](https://engagements.infillion.com/showcase/) shows working examples of TrueX and IDVx ads on all supported devices and platforms. If the ad experience does not load right away in the demo environment, refresh the page and it should load properly. --- --- url: /ads/publisher/platforms/android.md --- # Android Integration The TrueX Ad Renderer (TAR) for Android renders Infillion interactive ads - [TrueX](./truex) and [IDVx](./idvx) - inside native Android apps that play long-form video with ad breaks, known as FEP (Full Episode Player) apps. ## One SDK for TV and Mobile There is a single Android SDK, `TruexAdRenderer-Android`, and the same artifact and API are used on every Android-based device: * Android TV and Google TV * Fire TV (Android-based Fire OS apps) * Android phones and tablets The integration flow, methods, and events are the same everywhere. The few differences are noted where they apply, for example [`POPUP_WEBSITE`](./events#popup-website), which is fired on mobile only. ## TrueX and IDVx Both ad products are requested with a VAST tag in one of two formats, generic or companion - see [VAST Tag Types](./vast-tags). The same `TruexAdRenderer` renders both, and your app tells them apart by the VAST `` value: | | [TrueX](./truex) | [IDVx](./idvx) | | --- | --- | --- | | `` | `trueX` | `IDVx` | | Entry | Opt-in through a choice card | Starts automatically | | Pod position | First ad in the pod only | Any slot | | Reward | `AD_FREE_POD` - the remaining ads in the pod are skipped | None - the pod continues | ## What You'll Need * An Android app with a video player and an ad integration (Google IMA CSAI, Google IMA DAI/SSAI, or your own ad layer) * `TruexAdRenderer-Android` `2.10.4` - see [SDK Integration](./sdk-integration) * A full-screen `ViewGroup` above your video player where the renderer can show the interactive ad * A test VAST tag from your Infillion contact - see [Integration Flow](./integration-flow) ## Next Steps * [Integration Flow](./integration-flow) - what Infillion provides, and what your app has to build * [SDK Integration](./sdk-integration) - adding the SDK and calling `TruexAdRenderer` * [Events](./events) - every event the renderer fires * [Examples](./examples/tv) - runnable reference apps for [TV](./examples/tv) and [mobile](./examples/mobile) --- --- url: /ads/publisher/platforms/android/vast-tags.md --- # VAST Tag Types Infillion uses two types of VAST tags for both TrueX and Interactive Video products. The tags differ in how the `adParameters` JSON payload is embedded in the VAST XML. ### "Generic" Tag The `adParameters` are embedded directly as a JSON string within the `` node: **TrueX "Generic" Tag:** ``` https://get.truex.com/{placement_hash}/vast/generic?{ad_request_parameters} ``` **Interactive Video "Generic" Tag:** ``` https://get.truex.com/{placement_hash}/vast/idvx/generic?{ad_request_parameters} ``` **VAST Structure:** ```xml trueX 00:00:30 ``` ### "Companion" Tag The `adParameters` are embedded in a companion ad's `` node as base64-encoded json string: **TrueX "Companion" Tag:** ``` https://get.truex.com/{placement_hash}/vast/companion?{ad_request_parameters} ``` **IDVx "Companion" Tag:** ``` https://get.truex.com/{placement_hash}/vast/idvx/companion?{ad_request_parameters} ``` **VAST Structure:** ```xml trueX 00:00:30 ``` ### Differentiating TrueX and IDVx The `` node value identifies the ad type: * **TrueX ads**: `trueX` * **IDVx ads**: `IDVx` Both tag types (Generic and Companion) work with both TrueX and IDVx products. --- --- url: /ads/publisher/platforms/android/integration-flow.md --- # Integration Flow An Android 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. :::steps 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, for example with Google IMA DAI. Both formats are described in [VAST Tag Types](./vast-tags). 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 can be placed in any slot. 3. The publisher integrates the SDK Your app [resolves `adParameters`](#resolving-adparameters) from the ad, [starts the renderer](#starting-the-renderer) when the ad plays, and [handles the terminal events](#handling-terminal-events). ::: ## Resolving `adParameters` The renderer is initialized with an `adParameters` `JSONObject`. Where that JSON comes from depends on the VAST tag format and the ad framework your app uses: | Tag format | Where `adParameters` is in the VAST | Google IMA (CSAI and DAI) | Custom VAST parsing | | --- | --- | --- | --- | | Generic | `` as a JSON string | `ad.traffickingParameters` | Text content of `` | | Companion | `` as a base64 JSON `data:` URL | `ad.companionAds` (`apiFramework` is `truex`) | Text content of ``, base64-decoded after the comma | 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 `` JSON. 3. If neither one gives valid JSON, do not start the renderer. Treat the ad as a normal linear ad and continue the pod. Before resolving the payload, identify the ad by its `` value (`ad.adSystem` in Google IMA). `trueX` is a TrueX ad and `IDVx` is an IDVx 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. The [TV examples](./examples/tv#resolving-adparameters) contain complete resolvers for Google IMA and for custom VAST parsing. ## Starting the Renderer When the Infillion ad starts playing: 1. Pause the ad playback (for Google IMA, pause the `AdsManager` or the stream) and hide the video player. 2. Create a `TruexAdRenderer`, add your event listener, and call `init` with the `adParameters` JSON. 3. Call `start` with the full-screen `ViewGroup` above your player. 4. Wait for a terminal event. While you wait, keep track of whether `AD_FREE_POD` has fired. The calls are described in [SDK Integration](./sdk-integration). ## 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 result | What to do | CSAI (e.g. Google IMA) | SSAI (e.g. Google IMA DAI) | | --- | --- | --- | --- | | `AD_COMPLETED` after `AD_FREE_POD` | Skip all remaining ads in the pod and continue the content | Discard the ad break (`adsManager.discardAdBreak()`) | Seek to the ad break start plus the break duration | | `AD_COMPLETED` without `AD_FREE_POD` | Skip the Infillion placeholder ad and play the next ad in the pod | Finish the placeholder ad and resume the ad manager | Seek to the end of the placeholder ad | | `AD_ERROR` or `NO_ADS_AVAILABLE` | Same as `AD_COMPLETED` without `AD_FREE_POD` | Same | Same | | `USER_CANCEL_STREAM` | The user pressed Back during the Infillion ad - exit the playback screen | Close the player | Close the player | `AD_COMPLETED` without `AD_FREE_POD` covers a TrueX user who opted out or let the choice card time out, and it is the normal outcome of every IDVx ad, because IDVx never fires `AD_FREE_POD`. > \[!IMPORTANT] > Do not seek or resume playback when `AD_FREE_POD` fires. Remember that it fired, and act only on the terminal event that follows. After any terminal event, remove your listeners, call `stop()`, and drop all references to the renderer instance. The full list of terminal events is on the [Events](./events#terminal-events) page. --- --- url: /ads/publisher/platforms/android/sdk-integration.md --- # SDK Integration ## Adding the SDK `TruexAdRenderer-Android` is distributed through the Infillion Maven repository: * Repository: `https://s3.amazonaws.com/android.truex.com/tar/prod/maven` * Group ID: `com.truex` * Artifact ID: `TruexAdRenderer-Android` * Version: `2.10.4` ::: code-group ```kotlin [settings.gradle.kts] dependencyResolutionManagement { repositories { google() mavenCentral() maven("https://s3.amazonaws.com/android.truex.com/tar/prod/maven") } } ``` ```groovy [settings.gradle] dependencyResolutionManagement { repositories { google() mavenCentral() maven { url "https://s3.amazonaws.com/android.truex.com/tar/prod/maven" } } } ``` ::: ::: code-group ```kotlin [build.gradle.kts] dependencies { implementation("com.truex:TruexAdRenderer-Android:2.10.4") } ``` ```groovy [build.gradle] dependencies { implementation 'com.truex:TruexAdRenderer-Android:2.10.4' } ``` ::: The SDK classes are in the `com.truex.adrenderer` package: * `TruexAdRenderer` - renders the ad and is controlled by your app * `TruexAdOptions` - configuration options for `init` * `TruexAdEvent` - the events the renderer fires, see [Events](./events) * `IEventEmitter.IEventHandler` - the event listener interface ## Renderer Container The renderer shows the ad in a `ViewGroup` that you provide. Place a full-screen container above your video player: ```xml ``` Hide as much other UI as possible while the ad is showing, including player controls and status bars. ## Quick Example A complete start of an Infillion ad, where `adParameters` is the JSON [resolved from the VAST response](./integration-flow#resolving-adparameters): ::: code-group ```kotlin [Kotlin] private var truexAdRenderer: TruexAdRenderer? = null private var truexAdCreditReceived = false private val truexAdEventHandler = IEventEmitter.IEventHandler { event, data -> when (event) { TruexAdEvent.AD_FREE_POD -> { // Remember the reward, act on it on AD_COMPLETED truexAdCreditReceived = true } TruexAdEvent.AD_COMPLETED -> { finishInfillionAd(skipRemainingAds = truexAdCreditReceived) } TruexAdEvent.AD_ERROR, TruexAdEvent.NO_ADS_AVAILABLE -> { finishInfillionAd(skipRemainingAds = false) } TruexAdEvent.USER_CANCEL_STREAM -> { exitPlayback() } else -> Unit } } private fun startInfillionAd(adParameters: JSONObject) { truexAdCreditReceived = false val options = TruexAdOptions().apply { supportsUserCancelStream = true appId = packageName enableWebViewDebugging = BuildConfig.DEBUG } truexAdRenderer = TruexAdRenderer(this).also { renderer -> renderer.addEventListener(null, truexAdEventHandler) renderer.init(adParameters, options) renderer.start(binding.adContainer) } } private fun disposeRenderer() { truexAdRenderer?.removeEventListener(null, truexAdEventHandler) truexAdRenderer?.stop() truexAdRenderer = null } ``` ```java [Java] private TruexAdRenderer truexAdRenderer; private boolean truexAdCreditReceived = false; private final IEventEmitter.IEventHandler truexAdEventHandler = (event, data) -> { switch (event) { case AD_FREE_POD: // Remember the reward, act on it on AD_COMPLETED truexAdCreditReceived = true; break; case AD_COMPLETED: finishInfillionAd(truexAdCreditReceived); break; case AD_ERROR: case NO_ADS_AVAILABLE: finishInfillionAd(false); break; case USER_CANCEL_STREAM: exitPlayback(); break; default: break; } }; private void startInfillionAd(JSONObject adParameters) { truexAdCreditReceived = false; TruexAdOptions options = new TruexAdOptions(); options.supportsUserCancelStream = true; options.appId = getPackageName(); options.enableWebViewDebugging = BuildConfig.DEBUG; truexAdRenderer = new TruexAdRenderer(this); truexAdRenderer.addEventListener(null, truexAdEventHandler); truexAdRenderer.init(adParameters, options); truexAdRenderer.start(adContainer); } private void disposeRenderer() { if (truexAdRenderer != null) { truexAdRenderer.removeEventListener(null, truexAdEventHandler); truexAdRenderer.stop(); truexAdRenderer = null; } } ``` ::: `finishInfillionAd` and `exitPlayback` are your app's code - what they have to do is described in [Handling Terminal Events](./integration-flow#handling-terminal-events). ## `TruexAdRenderer` Methods ### `init` ::: code-group ```kotlin [Kotlin] fun init(adParameters: JSONObject) fun init(adParameters: JSONObject, options: TruexAdOptions) ``` ```java [Java] public void init(JSONObject adParameters) public void init(JSONObject adParameters, TruexAdOptions options) ``` ::: Initializes the renderer with the `adParameters` JSON and starts the ad request to the Infillion ad server. The renderer fires [`AD_FETCH_COMPLETED`](./events#ad-fetch-completed) when the request is done. You can call `init` early, a few seconds before the ad break starts, to give the renderer more time for the request, and call `start` once the player reaches the ad. The parameters are: * `adParameters`: the JSON payload from the VAST response, see [Resolving `adParameters`](./integration-flow#resolving-adparameters) * `options`: an optional [`TruexAdOptions`](#truexadoptions) object ### `start` ::: code-group ```kotlin [Kotlin] fun start(baseView: ViewGroup) ``` ```java [Java] public void start(ViewGroup baseView) ``` ::: Shows the ad to the user. Call it when your app is ready to display the ad; it can be called any time after `init`. If the ad request from `init` is not finished yet, the renderer waits for it, then adds its view to `baseView` and fires [`AD_STARTED`](./events#ad-started). After calling `start`, wait for a [terminal event](./events#terminal-events) and keep track of whether [`AD_FREE_POD`](./events#ad-free-pod) has fired. * `baseView`: the `ViewGroup` the renderer adds its view to. It should cover the entire screen. ### `stop` ::: code-group ```kotlin [Kotlin] fun stop() ``` ```java [Java] public void stop() ``` ::: Stops the renderer immediately and releases its resources. Call it after a terminal event, and also when: * the user leaves the playback screen while the ad is showing * an unrelated error requires your app to stop the ad * your own timeout for `AD_FETCH_COMPLETED` or `AD_STARTED` is reached A stopped renderer cannot be resumed or reused - remove all references to it. ### `pause` ::: code-group ```kotlin [Kotlin] fun pause() ``` ```java [Java] public void pause() ``` ::: Pauses the ad's videos, audio, and timers. Call it when your app is paused, for example from `onPause`, and when your app shows a popup for [`POPUP_WEBSITE`](./events#popup-website). ### `resume` ::: code-group ```kotlin [Kotlin] fun resume() ``` ```java [Java] public void resume() ``` ::: Resumes a paused renderer. ### `addEventListener` / `removeEventListener` ::: code-group ```kotlin [Kotlin] fun addEventListener(event: TruexAdEvent?, handler: IEventEmitter.IEventHandler) fun removeEventListener(event: TruexAdEvent?, handler: IEventEmitter.IEventHandler) ``` ```java [Java] public void addEventListener(TruexAdEvent event, IEventEmitter.IEventHandler handler) public void removeEventListener(TruexAdEvent event, IEventEmitter.IEventHandler handler) ``` ::: Registers or removes a listener for one event. Pass `null` as the event to register one handler for all events, as in the [Quick Example](#quick-example). See [Events](./events) for the full list. ## `TruexAdOptions` | Option | Default | Description | | --- | --- | --- | | `supportsUserCancelStream` | `false` | If `true`, a Back press during the ad fires [`USER_CANCEL_STREAM`](./events#user-cancel-stream), so your app can exit the stream. If `false`, Back on a TrueX choice card fires `OPT_OUT` and then `AD_COMPLETED`, and Back during an IDVx ad does nothing. | | `appId` | package name | Used for internal tracking. Computed from the context's `getPackageName()` if not set. | | `enableWebViewDebugging` | `false` | If `true`, remote WebView debugging through `chrome://inspect` is enabled, even for release builds. Otherwise it is allowed only for debug builds and for QA ads (from `qa-get.truex.com`). | ## Activity Lifecycle Forward your activity lifecycle to the renderer while it is active, and release it when the activity is destroyed: ::: code-group ```kotlin [Kotlin] override fun onPause() { if (truexAdRenderer != null) { truexAdRenderer?.pause() } else { player.pause() } super.onPause() } override fun onResume() { super.onResume() if (truexAdRenderer != null) { truexAdRenderer?.resume() } else { player.play() } } override fun onDestroy() { disposeRenderer() player.release() super.onDestroy() } ``` ```java [Java] @Override protected void onPause() { if (truexAdRenderer != null) { truexAdRenderer.pause(); } else { player.pause(); } super.onPause(); } @Override protected void onResume() { super.onResume(); if (truexAdRenderer != null) { truexAdRenderer.resume(); } else { player.play(); } } @Override protected void onDestroy() { disposeRenderer(); player.release(); super.onDestroy(); } ``` ::: --- --- url: /ads/publisher/platforms/android/events.md --- # Events `TruexAdRenderer` tells your app about the ad lifecycle through `TruexAdEvent` events. Each handler receives the event and a `Map` with the event's data. You can listen to a single event, or pass `null` to handle all events in one place: ::: code-group ```kotlin [Kotlin] truexAdRenderer.addEventListener(TruexAdEvent.AD_STARTED) { event, data -> // ... } truexAdRenderer.addEventListener(null) { event, data -> when (event) { TruexAdEvent.AD_STARTED -> { // ... } // ...other events else -> Unit } } ``` ```java [Java] truexAdRenderer.addEventListener(TruexAdEvent.AD_STARTED, (event, data) -> { // ... }); truexAdRenderer.addEventListener(null, (event, data) -> { switch (event) { case AD_STARTED: // ... break; // ...other events default: break; } }); ``` ::: ## Event Flow A typical TrueX ad where the user opts in and earns the reward: ``` ◇ AD_FETCH_COMPLETED │ ↓ ◇ AD_STARTED │ ↓ ◇ AD_DISPLAYED │ ↓ ◇ OPT_IN │ ↓ ◆ [User interacts with the ad] │ ↓ ◇ AD_FREE_POD │ ↓ ◇ AD_COMPLETED ``` An IDVx ad has no choice card and no reward, so it goes from `AD_DISPLAYED` straight to `AD_COMPLETED`, without `OPT_IN`, `OPT_OUT`, or `AD_FREE_POD`. ## Terminal Events A terminal event means the renderer is done, and your app must continue playback - see [Handling Terminal Events](./integration-flow#handling-terminal-events) for what to do in each case: * [`AD_COMPLETED`](#ad-completed) - the ad is finished * [`AD_ERROR`](#ad-error) - the renderer hit an unrecoverable error * [`NO_ADS_AVAILABLE`](#no-ads-available) - there is no ad for this user * [`USER_CANCEL_STREAM`](#user-cancel-stream) - the user wants to exit the stream After a terminal event, call `stop()` and remove all references to the renderer instance. ## Main Flow Events These events drive the integration and may require action from your app. ### `AD_FETCH_COMPLETED` Fired after `init` when the ad request is done and the ad is ready to be shown. You can use it for a loading screen or a timeout. For example, call `init` before a midroll and show the ad with `start` once this event arrives; if your timeout is reached first, call `stop()` and continue with the next ad. ### `AD_STARTED` Fired after `start` when the renderer's view is added to the view hierarchy. ### `AD_DISPLAYED` Fired after `AD_STARTED` when the initial ad assets are loaded and visible on the screen. This is useful only if your app shows its own loading screen instead of the renderer's loading spinner: hide your loading screen when this event arrives. ### `AD_COMPLETED` **Terminal event.** Fired when the ad is finished, for example when: * the TrueX user chooses the normal video ads, or the choice card countdown runs out * the TrueX user completes the ad and exits it * a TrueX skip card has been shown for its duration * an IDVx ad ends | Data key | Type | Description | | --- | --- | --- | | `timeSpent` | `Int` | Time in seconds the user spent in the ad | ::: code-group ```kotlin [Kotlin] truexAdRenderer.addEventListener(TruexAdEvent.AD_COMPLETED) { _, data -> val timeSpent = data?.get("timeSpent") as? Int finishInfillionAd(skipRemainingAds = truexAdCreditReceived) } ``` ```java [Java] truexAdRenderer.addEventListener(TruexAdEvent.AD_COMPLETED, (event, data) -> { Integer timeSpent = (Integer) data.get("timeSpent"); finishInfillionAd(truexAdCreditReceived); }); ``` ::: ### `AD_ERROR` **Terminal event.** Fired when the renderer hits an unrecoverable error. Handle it like `AD_COMPLETED` without `AD_FREE_POD`. | Data key | Type | Description | | --- | --- | --- | | `errorMessage` | `String` | Description of the error | ::: code-group ```kotlin [Kotlin] truexAdRenderer.addEventListener(TruexAdEvent.AD_ERROR) { _, data -> val errorMessage = data?.get("errorMessage") as? String finishInfillionAd(skipRemainingAds = false) } ``` ```java [Java] truexAdRenderer.addEventListener(TruexAdEvent.AD_ERROR, (event, data) -> { String errorMessage = (String) data.get("errorMessage"); finishInfillionAd(false); }); ``` ::: ### `NO_ADS_AVAILABLE` **Terminal event.** Fired when there is no ad available for this user. Handle it like `AD_COMPLETED` without `AD_FREE_POD`. ### `AD_FREE_POD` Fired when a TrueX user has earned the reward. **Do not resume or seek yet** - remember that it fired, and when the terminal `AD_COMPLETED` arrives, skip all remaining ads in the pod. IDVx ads never fire this event. ::: code-group ```kotlin [Kotlin] truexAdRenderer.addEventListener(TruexAdEvent.AD_FREE_POD) { _, _ -> truexAdCreditReceived = true } ``` ```java [Java] truexAdRenderer.addEventListener(TruexAdEvent.AD_FREE_POD, (event, data) -> { truexAdCreditReceived = true; }); ``` ::: ### `POPUP_WEBSITE` Fired when the ad wants to open a web page. Call `pause()` on the renderer and show the URL to the user, for example in a browser. When the user comes back, call `resume()`. This event is fired on mobile only. | Data key | Type | Description | | --- | --- | --- | | `url` | `String` | The URL to open | ::: code-group ```kotlin [Kotlin] truexAdRenderer.addEventListener(TruexAdEvent.POPUP_WEBSITE) { _, data -> val url = data?.get("url") as? String truexAdRenderer?.pause() startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url))) } ``` ```java [Java] truexAdRenderer.addEventListener(TruexAdEvent.POPUP_WEBSITE, (event, data) -> { String url = (String) data.get("url"); truexAdRenderer.pause(); startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url))); }); ``` ::: ### `USER_CANCEL_STREAM` **Terminal event.** Fired when the user presses Back during the Infillion ad because they want to exit the stream. Handle it like any other exit from your player - usually by returning the user to the episode or series page. This event is fired only if [`supportsUserCancelStream`](./sdk-integration#truexadoptions) is `true`. Otherwise, Back on a TrueX choice card fires `OPT_OUT` and then `AD_COMPLETED`. ## Informative Events These events are mostly useful for tracking. Your app does not need to act on them. ### `OPT_IN` Fired when the user chooses the TrueX interactive ad on the choice card. It can fire more than once if the user opts in, backs out, and opts in again. ### `OPT_OUT` Fired when the user chooses the normal video ads on the TrueX choice card. | Data key | Type | Description | | --- | --- | --- | | `userInitiated` | `Boolean` | `true` if the user selected it, `false` if the choice card countdown ran out | ### `SKIP_CARD_SHOWN` Fired when a skip card is shown, because the user completed a TrueX Sponsored Stream ad earlier in the stream. ### `USER_CANCEL` Fired when the user backs out of the TrueX interactive ad after opting in, by confirming the "go back and choose a different ad experience" prompt. The user returns to the choice card with a full countdown, so more `OPT_IN` or `OPT_OUT` events can follow. --- --- url: /ads/publisher/platforms/android/examples/tv.md --- # TV Examples The [infillion-ads-android-examples][repo] repository contains `kotlin-ctv-app`, an Android TV app with three complete integrations of TrueX and IDVx ads into a Media3 player: | Example | Ad source | Midroll behavior | | --- | --- | --- | | [Manual CSAI](#manual-csai) | VAST tags from a bundled JSON ad break | The app schedules and plays the ad pod itself | | [Google IMA CSAI](#google-ima-csai) | A hosted VAST request | Google IMA requests and sequences client-side ads | | [Google IMA SSAI](#google-ima-ssai) | A Google DAI VOD stream | Google DAI returns one stream with stitched ad breaks | Each example has its own player, ad, and `TruexAdRenderer` code on purpose, so you can pick the one closest to your setup and read it from top to bottom. ## Running the App Requirements: * Android Studio with JDK 17 * Android SDK 35 * An Android TV device or emulator running API 28 or newer Open the repository in Android Studio and run the `kotlin-ctv-app` configuration, or build it from the command line: ```bash ./gradlew assembleDebug ``` Then install `kotlin-ctv-app/build/outputs/apk/debug/kotlin-ctv-app-debug.apk` on the device. Use the D-pad to pick an example; each one shows its current state (content, linear ad, interactive ad, errors) in a status panel. > \[!NOTE] Test IP parameter > The sample VAST tags include `&ip=158.106.195.210`, the IP of the Infillion NYC office. The TrueX ad server serves ads in the US and Canada only, and this parameter lets teams and CI runners outside those regions get filled test ads. Do not use it in production. ## Resolving `adParameters` All three examples resolve `adParameters` in the same order - the `truex` companion first, then `` - as described in [Integration Flow](../integration-flow#resolving-adparameters). With Google IMA (CSAI and DAI) the resolver looks like this: ::: code-group ```kotlin [Kotlin] fun extractAdParameters( companionAds: List?, traffickingParameters: String?, ): JSONObject? { // 1. Companion tag: base64 JSON data URL in companionAds?.forEach { companion -> if (companion.apiFramework.equals("truex", ignoreCase = true)) { val resource = companion.resourceValue.filterNot { it.isWhitespace() } val json = runCatching { String(Base64.getDecoder().decode(resource.substringAfter(',')), Charsets.UTF_8) }.getOrNull() val parsed = json?.let { runCatching { JSONObject(it) }.getOrNull() } if (parsed != null) { return parsed } } } // 2. Generic tag: , exposed by IMA as traffickingParameters if (!traffickingParameters.isNullOrBlank()) { return runCatching { JSONObject(traffickingParameters) }.getOrNull() } return null } ``` ```java [Java] @Nullable static JSONObject extractAdParameters( @Nullable List companionAds, @Nullable String traffickingParameters) { // 1. Companion tag: base64 JSON data URL in if (companionAds != null) { for (CompanionAd companion : companionAds) { if (!"truex".equalsIgnoreCase(companion.getApiFramework())) { continue; } String resource = companion.getResourceValue().replaceAll("\\s", ""); String encoded = resource.substring(resource.indexOf(',') + 1); try { byte[] decoded = Base64.getDecoder().decode(encoded); return new JSONObject(new String(decoded, StandardCharsets.UTF_8)); } catch (IllegalArgumentException | JSONException e) { // Not a valid companion payload, try the next source } } } // 2. Generic tag: , exposed by IMA as traffickingParameters if (traffickingParameters != null && !traffickingParameters.trim().isEmpty()) { try { return new JSONObject(traffickingParameters); } catch (JSONException e) { return null; } } return null; } ``` ::: The Manual CSAI example does the same with the raw VAST XML, reading `` / `` and `` directly - see [`ManualVastPayload.kt`][manual_payload]. ## Manual CSAI Use this example when your app or your own ad layer decides when an ad break starts and which ads it contains. 1. Before content starts, the app fetches each Infillion VAST tag from the ad break fixture ([`manual_ad_break.json`][manual_fixture]) and resolves its `adParameters`. 2. At the midroll, the app saves the content position and plays the ads of the pod one by one in the same player. 3. For a TrueX or IDVx ad, the app does not play the placeholder video: it stops and hides the player and starts `TruexAdRenderer` in the overlay container. 4. On `AD_COMPLETED` after `AD_FREE_POD`, the app ends the pod and returns to the content. Otherwise (opt-out, error, no ads, or IDVx) the next ad in the pod starts. 5. After the pod, the content is restored at its saved position. Files: [`ManualCsaiActivity.kt`][manual_activity], [`ManualVastPayload.kt`][manual_payload], [`ManualAdBreak.kt`][manual_break] - guide: [manual-csai.md][manual_doc] ## Google IMA CSAI Use this example when Google IMA requests and plays your client-side ads. 1. On the IMA `STARTED` event, the app checks `ad.adSystem`. A TrueX ad that is not the first in the pod (`adPodInfo.adPosition != 1`) plays as a normal linear ad. 2. For an Infillion ad, the app resolves `adParameters` from `ad.companionAds` / `ad.traffickingParameters`, pauses the `AdsManager` and the player, and starts the renderer. 3. On `AD_COMPLETED` after `AD_FREE_POD`, the app calls `adsManager.discardAdBreak()` and resumes. Otherwise it finishes the placeholder ad so IMA continues with the next ad in the pod. ::: code-group ```kotlin [Kotlin] private fun finishInteractive(shouldSkipPod: Boolean) { disposeRenderer() binding.playerView.visibility = View.VISIBLE if (shouldSkipPod) { // TrueX reward earned: discard the rest of the client-side ad break adsManager?.discardAdBreak() } else { // Opt-out, error, or IDVx: finish the placeholder and play the next ad videoPlayer.finishCurrentAd() } adsManager?.resume() } ``` ```java [Java] private void finishInteractive(boolean shouldSkipPod) { disposeRenderer(); playerView.setVisibility(View.VISIBLE); if (shouldSkipPod) { // TrueX reward earned: discard the rest of the client-side ad break adsManager.discardAdBreak(); } else { // Opt-out, error, or IDVx: finish the placeholder and play the next ad videoPlayer.finishCurrentAd(); } adsManager.resume(); } ``` ::: Files: [`ImaCsaiActivity.kt`][ima_csai_activity], [`ImaCsaiAdPayload.kt`][ima_csai_payload], [`ImaCsaiVideoPlayer.kt`][ima_csai_player] - guide: [ima-csai.md][ima_csai_doc] ## Google IMA SSAI Use this example when Google IMA DAI returns a single stream with stitched ad breaks. 1. On the IMA `STARTED` event, the app tracks where the current placeholder ad ends in the stream (`adPodInfo.timeOffset` plus the ad durations), and checks `ad.adSystem` and the pod position like the CSAI example. 2. For an Infillion ad, the app resolves `adParameters`, pauses and hides the stream, and starts the renderer. 3. Google DAI has no `discardAdBreak()`, so the app moves the timeline by seeking. On `AD_COMPLETED` after `AD_FREE_POD`, it seeks past the whole ad break. Otherwise it seeks to just before the end of the placeholder ad, so the next stitched ad plays. ::: code-group ```kotlin [Kotlin] private fun finishInteractive(shouldSkipPod: Boolean) { disposeRenderer() videoStreamPlayer.show() val pod = streamManager?.currentAd?.adPodInfo val progress = streamManager?.adProgressInfo if (shouldSkipPod && pod != null && progress != null) { // TrueX reward earned: seek past the whole stitched ad break val breakEndMs = ((pod.timeOffset + progress.adBreakDuration + 2.0) * 1_000).toLong() videoStreamPlayer.seekTo(breakEndMs) } else { // Opt-out, error, or IDVx: seek to the placeholder end, the next stitched ad plays videoStreamPlayer.seekTo((currentAdEndMs - 100L).coerceAtLeast(0L)) } videoStreamPlayer.resume() } ``` ```java [Java] private void finishInteractive(boolean shouldSkipPod) { disposeRenderer(); videoStreamPlayer.show(); Ad currentAd = streamManager != null ? streamManager.getCurrentAd() : null; AdPodInfo pod = currentAd != null ? currentAd.getAdPodInfo() : null; AdProgressInfo progress = streamManager != null ? streamManager.getAdProgressInfo() : null; if (shouldSkipPod && pod != null && progress != null) { // TrueX reward earned: seek past the whole stitched ad break long breakEndMs = (long) ((pod.getTimeOffset() + progress.getAdBreakDuration() + 2.0) * 1_000); videoStreamPlayer.seekTo(breakEndMs); } else { // Opt-out, error, or IDVx: seek to the placeholder end, the next stitched ad plays videoStreamPlayer.seekTo(Math.max(currentAdEndMs - 100L, 0L)); } videoStreamPlayer.resume(); } ``` ::: Files: [`ImaSsaiActivity.kt`][ima_ssai_activity], [`ImaSsaiAdPayload.kt`][ima_ssai_payload], [`ImaSsaiVideoStreamPlayer.kt`][ima_ssai_player] - guide: [ima-ssai.md][ima_ssai_doc] [repo]: https://github.com/socialvibe/infillion-ads-android-examples [manual_fixture]: https://github.com/socialvibe/infillion-ads-android-examples/blob/main/kotlin-ctv-app/src/main/res/raw/manual_ad_break.json [manual_activity]: https://github.com/socialvibe/infillion-ads-android-examples/blob/main/kotlin-ctv-app/src/main/java/com/infillion/truex/reference/manualcsai/ManualCsaiActivity.kt [manual_payload]: https://github.com/socialvibe/infillion-ads-android-examples/blob/main/kotlin-ctv-app/src/main/java/com/infillion/truex/reference/manualcsai/ManualVastPayload.kt [manual_break]: https://github.com/socialvibe/infillion-ads-android-examples/blob/main/kotlin-ctv-app/src/main/java/com/infillion/truex/reference/manualcsai/ManualAdBreak.kt [manual_doc]: https://github.com/socialvibe/infillion-ads-android-examples/blob/main/docs/manual-csai.md [ima_csai_activity]: https://github.com/socialvibe/infillion-ads-android-examples/blob/main/kotlin-ctv-app/src/main/java/com/infillion/truex/reference/imacsai/ImaCsaiActivity.kt [ima_csai_payload]: https://github.com/socialvibe/infillion-ads-android-examples/blob/main/kotlin-ctv-app/src/main/java/com/infillion/truex/reference/imacsai/ImaCsaiAdPayload.kt [ima_csai_player]: https://github.com/socialvibe/infillion-ads-android-examples/blob/main/kotlin-ctv-app/src/main/java/com/infillion/truex/reference/imacsai/ImaCsaiVideoPlayer.kt [ima_csai_doc]: https://github.com/socialvibe/infillion-ads-android-examples/blob/main/docs/ima-csai.md [ima_ssai_activity]: https://github.com/socialvibe/infillion-ads-android-examples/blob/main/kotlin-ctv-app/src/main/java/com/infillion/truex/reference/imassai/ImaSsaiActivity.kt [ima_ssai_payload]: https://github.com/socialvibe/infillion-ads-android-examples/blob/main/kotlin-ctv-app/src/main/java/com/infillion/truex/reference/imassai/ImaSsaiAdPayload.kt [ima_ssai_player]: https://github.com/socialvibe/infillion-ads-android-examples/blob/main/kotlin-ctv-app/src/main/java/com/infillion/truex/reference/imassai/ImaSsaiVideoStreamPlayer.kt [ima_ssai_doc]: https://github.com/socialvibe/infillion-ads-android-examples/blob/main/docs/ima-ssai.md --- --- url: /ads/publisher/platforms/android/examples/mobile.md --- # Mobile Examples The [infillion-ads-android-examples][repo] repository contains two phone and tablet apps with the same integration of TrueX and IDVx ads into a Media3 player - one in Kotlin (`kotlin-mobile-app`) and one in Java (`java-mobile-app`): | Example | Ad source | Midroll behavior | | --- | --- | --- | | [Manual CSAI](#manual-csai) | VAST tags from a bundled JSON ad break | The app schedules and plays the ad pod itself | Both apps have the same screens, flow, and sample configuration; only the language differs. The flow, VAST parsing, and renderer handling are the same as the [Manual CSAI TV example](./tv#manual-csai) - only the screen layout and the touch and lifecycle handling are mobile-specific. Each example has its own player, ad, and `TruexAdRenderer` code on purpose, so you can copy one package and read it from top to bottom. ## Running the Apps Requirements: * Android Studio with JDK 17 * Android SDK 35 * A phone or tablet device or emulator running API 28 or newer Open the repository in Android Studio and run the `kotlin-mobile-app` or `java-mobile-app` configuration, or build them from the command line: ```bash ./gradlew assembleDebug ``` Then install `kotlin-mobile-app/build/outputs/apk/debug/kotlin-mobile-app-debug.apk` or `java-mobile-app/build/outputs/apk/debug/java-mobile-app-debug.apk` on the device. The two apps have different application IDs, so both can be installed side by side. Tap an example card; the example shows its current state (content, linear ad, interactive ad, errors) in a status panel, and logs it with the `InfillionAdsExamples` tag. > \[!NOTE] Test IP parameter > The sample VAST tags include `&ip=158.106.195.210`, the IP of the Infillion NYC office. The TrueX ad server serves ads in the US and Canada only, and this parameter lets teams and CI runners outside those regions get filled test ads. Do not use it in production. ## Manual CSAI Use this example when your app or your own ad layer decides when an ad break starts and which ads it contains. 1. Before content starts, the app fetches each Infillion VAST tag from the ad break fixture ([`manual_ad_break.json`][kotlin_fixture]) and resolves its `adParameters` - the `truex` companion first, then ``, as described in [Integration Flow](../integration-flow#resolving-adparameters). 2. At the midroll, the app saves the content position, turns off the touch controls, and plays the ads of the pod one by one in the same player. 3. For a TrueX or IDVx ad, the app does not play the placeholder video: it stops and hides the player, hides the toolbar and status panel, and starts `TruexAdRenderer` in the overlay container. 4. On `AD_COMPLETED` after `AD_FREE_POD`, the app ends the pod and returns to the content. Otherwise (opt-out, error, no ads, or IDVx) the next ad in the pod starts. 5. After the pod, the content is restored at its saved position and the touch controls come back. ::: code-group ```kotlin [Kotlin] private fun playCurrentAd() { val ad = currentAdOrNull() if (ad == null) { finishAdBreak("Ad pod complete") return } // The host owns playback, so interactive ads skip their placeholder video and start the renderer directly. if (ad.type != ManualAdType.LINEAR) { // Stop loading the paused media so it can't report errors while the renderer runs. player.stop() showInteractiveAd(ad) return } binding.playerView.visibility = View.VISIBLE player.setMediaItem(MediaItem.fromUri(ad.mediaUrl)) player.prepare() player.play() } ``` ```java [Java] private void playCurrentAd() { ManualAdBreak.Ad ad = currentAdOrNull(); if (ad == null) { finishAdBreak("Ad pod complete"); return; } // The host owns playback, so interactive ads skip their placeholder video and start the renderer directly. if (ad.type != ManualAdBreak.AdType.LINEAR) { // Stop loading the paused media so it can't report errors while the renderer runs. player.stop(); showInteractiveAd(ad); return; } playerView.setVisibility(View.VISIBLE); player.setMediaItem(MediaItem.fromUri(ad.mediaUrl)); player.prepare(); player.play(); } ``` ::: Files: * Kotlin: [`ManualCsaiActivity.kt`][kotlin_activity], [`ManualVastPayload.kt`][kotlin_payload], [`ManualAdBreak.kt`][kotlin_break] * Java: [`ManualCsaiActivity.java`][java_activity], [`ManualVastPayloadParser.java`][java_payload], [`ManualAdBreak.java`][java_break] * Guide: [mobile-manual-csai.md][mobile_doc] ## Mobile-Specific Notes * [`POPUP_WEBSITE`](../events#popup-website) is fired on mobile only. The examples open the URL with `Intent.ACTION_VIEW`; the activity's `onPause()` / `onResume()` pause and resume the renderer while the browser is in front. * `USER_CANCEL_STREAM` (Back on the choice card) closes the player screen. * Forward `onPause` / `onResume` to the renderer while it is active - see [Activity Lifecycle](../sdk-integration#activity-lifecycle). Without a renderer, resume the player only if it was playing before the pause, so a pause the viewer made with the touch controls is kept. * If the activity is paused while the VAST tags are loading, the examples start content in `onResume()` instead of playing in the background, and they drop the result if the activity was destroyed in the meantime. * The player activity handles orientation and screen-size changes itself (`android:configChanges`), so rotating the device does not recreate the player or the renderer during an ad. ::: code-group ```kotlin [Kotlin] override fun onResume() { super.onResume() if (startContentOnResume) { startContentOnResume = false startContent() } else if (truexAdRenderer != null) { truexAdRenderer?.resume() } else if (playWhenReadyBeforePause) { // Resume only what was playing before; keep a pause the viewer made with the touch controls. player.play() } } override fun onPause() { if (truexAdRenderer != null) { truexAdRenderer?.pause() } playWhenReadyBeforePause = player.playWhenReady player.pause() super.onPause() } ``` ```java [Java] @Override protected void onResume() { super.onResume(); if (startContentOnResume) { startContentOnResume = false; startContent(); } else if (truexAdRenderer != null) { truexAdRenderer.resume(); } else if (playWhenReadyBeforePause) { // Resume only what was playing before; keep a pause the viewer made with the touch controls. player.play(); } } @Override protected void onPause() { if (truexAdRenderer != null) { truexAdRenderer.pause(); } playWhenReadyBeforePause = player.getPlayWhenReady(); player.pause(); super.onPause(); } ``` ::: [repo]: https://github.com/socialvibe/infillion-ads-android-examples [kotlin_fixture]: https://github.com/socialvibe/infillion-ads-android-examples/blob/main/kotlin-mobile-app/src/main/res/raw/manual_ad_break.json [kotlin_activity]: https://github.com/socialvibe/infillion-ads-android-examples/blob/main/kotlin-mobile-app/src/main/java/com/infillion/ads/examples/kotlin/manualcsai/ManualCsaiActivity.kt [kotlin_payload]: https://github.com/socialvibe/infillion-ads-android-examples/blob/main/kotlin-mobile-app/src/main/java/com/infillion/ads/examples/kotlin/manualcsai/ManualVastPayload.kt [kotlin_break]: https://github.com/socialvibe/infillion-ads-android-examples/blob/main/kotlin-mobile-app/src/main/java/com/infillion/ads/examples/kotlin/manualcsai/ManualAdBreak.kt [java_activity]: https://github.com/socialvibe/infillion-ads-android-examples/blob/main/java-mobile-app/src/main/java/com/infillion/ads/examples/java/manualcsai/ManualCsaiActivity.java [java_payload]: https://github.com/socialvibe/infillion-ads-android-examples/blob/main/java-mobile-app/src/main/java/com/infillion/ads/examples/java/manualcsai/ManualVastPayloadParser.java [java_break]: https://github.com/socialvibe/infillion-ads-android-examples/blob/main/java-mobile-app/src/main/java/com/infillion/ads/examples/java/manualcsai/ManualAdBreak.java [mobile_doc]: https://github.com/socialvibe/infillion-ads-android-examples/blob/main/docs/mobile-manual-csai.md --- --- url: /ads/publisher/platforms/tvos/truex.md --- # TrueX ### Product Flow **TrueX** presents users with an **interactive choice card** where they choose between engaging with branded content or watching standard video ads. When users opt in and complete the interactive experience, they earn an **ad credit that skips all remaining ads** in the pod, creating a win-win: viewers get ad-free content, and advertisers get highly engaged audiences. **Key features:** * **Opt-in via choice card** - Users actively choose to engage * **Engagement requirements** - Users must spend 30 seconds and interact at least once to earn credit * **Skips entire ad break** - Successful engagement bypasses all remaining ads in the pod Upon completion, the renderer signals a `adFreePod` event, indicating that remaining linear ads should be skipped. There are two distinct product flows supported by `TruexAdRenderer`: Sponsored Stream (full-stream ad-replacement) and Sponsored Ad Break (mid-roll ad-replacement). In a Sponsored Ad Break flow, once the user hits a mid-roll break with a **TrueX** tag flighted, they will be shown a "choice-card" offering them the choice between watching a normal set of video ads or a fully interactive **TrueX** ad: ![choice card](/assets/images/choice_card.png) ***Fig. A** example **TrueX** mid-roll choice card* If the user opts for a normal ad break, or if the user does not make a selection before the countdown timer expires, the **TrueX** UI will close and playback of normal video ads can continue as usual. If the user opts to interact with **TrueX**, an interactive ad unit will be shown to the user: ![ad](/assets/images/ad.png) ***Fig. B** example **TrueX** interactive ad unit* The requirement for the user to "complete" this ad is for them to spend at least the allotted time on the unit and for at least one interaction (e.g. navigating anywhere through the ad). ![true attention timer example](/assets/images/true-attention-timer-example.png) ***Fig. C** example **TrueX** attention timer* Once the user fulfills both requirements, a "Watch Your Show" button will appear in the bottom right, which the user can select to exit the **TrueX** ad. Having completed a **TrueX** ad, the user will be returned directly to content, skipping the remaining ads in the current ad pod. The Sponsored Stream flow is quite similar. In this scenario, a user will be shown a choice-card in the pre-roll: ![choice card](/assets/images/choice_card.png) ***Fig. D** example **TrueX** pre-roll choice card (full-stream replacement)* Similarly, if the user opts-in and completes the **TrueX** ad, they will be skipped over the remainder of the pre-roll ad break. However, every subsequent mid-roll break in the current stream will also be skipped over. In this case instead of the regular pod of video ads, the user will be shown a "hero card" (also known as a "skip card"): ![skip card](/assets/images/skip_card.png) ***Fig. E** example **TrueX** mid-roll skip card* This messaging will be displayed to the user for several seconds, after which they will be returned directly to content. --- --- url: /ads/publisher/platforms/tvos/idvx.md --- # Interactive Video (IDVx) ### How Interactive Video (IDVx) Works **Interactive Video (IDVx)** delivers a new industry standard in video engagement across all devices and platforms. The product goes beyond hovers and inadvertent clicks, to deliver real interaction, by real humans, in premium environments. Built on the same core interactive foundation as TrueX, Interactive Video removes the opt-in, interaction, and time spent requirements, in order to capitalize on the scale of a Publisher's inventory. Infillion pays Publishers a set CPM for the IDVx ads the Publisher renders in their ad pods. **Key features:** * **Automatic start** - Begins playing without opt-in; there is no choice card * **Interactive throughout** - Users can interact with ad content during playback, but no interaction is required * **Capped at 30 seconds** - Unlike the unlimited time a user can spend in a TrueX ad * **Plays inline** - Completes and continues to the next ad in the pod; it does not skip or suppress the Publisher's ad pod ![Interactive Video ad pod flow](/assets/images/image5.jpg) ***Fig. A** example of an IDVx ad playing inline within a regular ad pod* #### User Flow 1. A viewer is streaming video content and reaches a commercial break 2. The content is paused to load the ad pod 3. The IDVx ad is loaded like any other ad, and is one of many in the Publisher's ad pod 4. When the user comes across the IDVx ad, they can interact with it if they choose 5. IDVx ads are 30 seconds long, so when those 30 seconds are up the ad ends 6. The video player then loads either another ad or the video content #### Supported Platforms IDVx ads are available on the following platforms: * [Vega (FireTV)](/platforms/vega/) * [Web/HTML](/platforms/web/) - desktop browsers and HTML5-based CTV apps * [Roku](/platforms/roku/) * [Android](/platforms/android/) - TV & mobile * [iOS](/platforms/ios/) * [tvOS](/platforms/tvos/) - coming soon ## Flighting the IDVx Placement Tag The IDVx placement tag is a 1 stage tag and can be flighted in every ad slot of a pod (except any reserved for TrueX) according to the priority the Publisher wants to give Infillion in filling that slot. For example the Publisher may wish to put the IDVx tag behind direct sold campaign tags, but ahead of programmatic ad tags. Or the Publisher may wish to give the IDVx tag the first position in every last slot of every pod. :::steps 1. The Publisher's AdOps team schedules the IDVx placement tag in any priority they wish throughout their network in their ad server 2. If the Publisher already has the TrueX SDK installed, they need to make a few changes to the integration code ([see below](#changes-existing-truex-publishers-need-to-make-to-support-idvx-ads)) 3. When the ad pod is triggered by the user, the IDVx ad tag will request an ad from the Infillion ad server (we support both CSAI and SSAI) 4. If a match is found for the user, the Infillion ad server will send an IDVx ad response that will trigger the IDVx user flow detailed above 5. If no IDVx ad is matched for the user by the Infillion ad server, an empty VAST response will be passed back, triggering whatever is scheduled next - either another video ad or the content ::: ::: warning Upcoming: SDK 4.x IDVx ads are supported by `TruexAdRenderer-tvOS` **4.x**, which is not released yet. SDK 3.x (`3.15.3`) does not support IDVx - keep treating an ad with `` `IDVx` as a regular linear ad until you upgrade. ::: ## Changes existing TrueX Publishers need to make to support IDVx ads When you move your tvOS app to SDK 4.x: 1. Allow the renderer to run for ads with `` `IDVx` in any pod position. The "first ad in the pod" rule applies to TrueX only. 2. Initialize the renderer with the `adParameters` JSON from the VAST response, the same way as for TrueX - see [SDK 4.x Integration](./v4/sdk-integration). 3. After an IDVx ad finishes, always continue with the next ad in the pod. IDVx never calls `onAdFreePod` - see [Integration Flow](./integration-flow#handling-terminal-events). ## Creative Example This animated gif illustrates the interactivity of an IDVx ad creative. ![Interactive Video creative example](/assets/images/image3.gif) > \[!TIP] > The [Infillion Engagement Showcase](https://engagements.infillion.com/showcase/) shows working examples of TrueX and IDVx ads on all supported devices and platforms. If the ad experience does not load right away in the demo environment, refresh the page and it should load properly. --- --- url: /ads/publisher/platforms/tvos.md --- # tvOS Integration The TrueX Ad Renderer (TAR) for tvOS renders Infillion interactive ads inside native Apple TV apps that play long-form video with ad breaks, known as FEP (Full Episode Player) apps. The renderer is available in two major versions: | SDK | Status | Ads | | --- | --- | --- | | `TruexAdRenderer-tvOS` **3.x** (`3.15.3`) | Current release | [TrueX](./truex) | | `TruexAdRenderer-tvOS` **4.x** | Upcoming, not released yet | [TrueX](./truex) and [IDVx](./idvx) | ## Supported Devices * Apple TV For iPhone and iPad, see the [iOS SDK](../ios/). ## TrueX and IDVx Both ad products are requested with a VAST tag in one of two formats, generic or companion - see [VAST Tag Types](./vast-tags). Your app tells them apart by the VAST `` value: | | [TrueX](./truex) | [IDVx](./idvx) | | --- | --- | --- | | `` | `trueX` | `IDVx` | | SDK | 3.x and 4.x | 4.x only | | Entry | Opt-in through a choice card | Starts automatically | | Pod position | First ad in the pod only | Any slot | | Reward | `onAdFreePod` - the remaining ads in the pod are skipped | None - the pod continues | ::: warning Upcoming: SDK 4.x SDK 4.x is not released yet. It fully supports both TrueX and IDVx ads. ::: ## What You'll Need * A tvOS app with a video player and an ad integration (Google IMA DAI, or your own ad layer) * tvOS 13.0 or later for SDK 3.x (the 4.x requirements will be published with its release) * `TruexAdRenderer-tvOS` `3.15.3` - see [SDK 3.x Integration](./v3/sdk-integration) - or `4.x` once released - see [SDK 4.x Integration](./v4/sdk-integration) * A full-screen view controller that the renderer can present the interactive ad on * A test VAST tag from your Infillion contact - see [Integration Flow](./integration-flow) ## Next Steps * [Integration Flow](./integration-flow) - what Infillion provides, and what your app has to build * SDK 3.x: [SDK Integration](./v3/sdk-integration), [Events](./v3/events), [Examples](./v3/examples) * SDK 4.x (upcoming): [SDK Integration](./v4/sdk-integration), [Events](./v4/events), [Examples](./v4/examples) --- --- url: /ads/publisher/platforms/tvos/vast-tags.md --- # VAST Tag Types Infillion uses two types of VAST tags for both TrueX and Interactive Video products. The tags differ in how the `adParameters` JSON payload is embedded in the VAST XML. ### "Generic" Tag The `adParameters` are embedded directly as a JSON string within the `` node: **TrueX "Generic" Tag:** ``` https://get.truex.com/{placement_hash}/vast/generic?{ad_request_parameters} ``` **Interactive Video "Generic" Tag:** ``` https://get.truex.com/{placement_hash}/vast/idvx/generic?{ad_request_parameters} ``` **VAST Structure:** ```xml trueX 00:00:30 ``` ### "Companion" Tag The `adParameters` are embedded in a companion ad's `` node as base64-encoded json string: **TrueX "Companion" Tag:** ``` https://get.truex.com/{placement_hash}/vast/companion?{ad_request_parameters} ``` **IDVx "Companion" Tag:** ``` https://get.truex.com/{placement_hash}/vast/idvx/companion?{ad_request_parameters} ``` **VAST Structure:** ```xml trueX 00:00:30 ``` ### Differentiating TrueX and IDVx The `` node value identifies the ad type: * **TrueX ads**: `trueX` * **IDVx ads**: `IDVx` Both tag types (Generic and Companion) work with both TrueX and IDVx products. --- --- url: /ads/publisher/platforms/tvos/integration-flow.md --- # 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. :::steps 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](./vast-tags). 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`](#resolving-adparameters) from the ad, [starts the renderer](#starting-the-renderer) when the ad plays, and [handles the terminal events](#handling-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 format | Where `adParameters` is in the VAST | Google IMA DAI | Custom VAST parsing | | --- | --- | --- | --- | | Generic | `` as a JSON string | `ad.traffickingParameters` | Text content of `` | | Companion | `` as a base64 JSON `data:` URL | Use the generic tag with Google IMA | Text content of ``, base64-decoded after the comma | First, identify the ad by its `` 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. ::: warning 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 `` 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: ::: code-group ```swift [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 `` (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 [Objective-C] // Parses `` (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](./v3/sdk-integration) or [4.x](./v4/sdk-integration). 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 result | What to do | Google IMA DAI (SSAI) | Custom ad layer | | --- | --- | --- | --- | | `onAdCompleted` after `onAdFreePod` | Skip all remaining ads in the pod and continue the content | Seek past the end of the current ad break's cue point | End the pod and restore the content | | `onAdCompleted` without `onAdFreePod` | Skip the Infillion placeholder ad and play the next ad in the pod | Seek to just before the placeholder's end, so the next stitched ad plays | Play the next ad in the pod | | `onAdError` or `onNoAdsAvailable` | Same as `onAdCompleted` without `onAdFreePod` | Same | Same | | `onUserCancelStream` | The user closed the Infillion ad because they want to leave - exit the playback screen | Close the player | Close 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](./v3/events#terminal-events) or [4.x](./v4/events#terminal-events). --- --- url: /ads/publisher/platforms/tvos/v3/sdk-integration.md --- # SDK 3.x Integration ## Adding the SDK `TruexAdRenderer-tvOS` 3.x is distributed through Swift Package Manager and CocoaPods. The current version is `3.15.3`, and it requires tvOS 13.0 or later. SDK 3.x renders [TrueX](../truex) ads. For IDVx, see [SDK 4.x](../v4/sdk-integration). ### Swift Package Manager 1. In Xcode, go to **File > Add Package Dependencies...** 2. Enter the repository URL: `https://github.com/socialvibe/TruexAdRenderer-tvOS-Swift-Package` 3. Select version `3.15.3` or later, and add the `TruexAdRenderer` product to your app target. Or, in a `Package.swift` manifest: ```swift dependencies: [ .package(url: "https://github.com/socialvibe/TruexAdRenderer-tvOS-Swift-Package", from: "3.15.3"), ], targets: [ .target( name: "MyApp", dependencies: [ .product(name: "TruexAdRenderer", package: "TruexAdRenderer-tvOS-Swift-Package"), ] ), ] ``` ### CocoaPods Add the TrueX spec repository ([socialvibe/cocoapod-specs](https://github.com/socialvibe/cocoapod-specs)) and the spec repository of the renderer's dependency next to the default one, then add the pod to your target: ```ruby source 'https://cdn.cocoapods.org/' source 'https://github.com/socialvibe/cocoapod-specs.git' source 'https://github.com/Innovid/cocoapods-spec.git' target 'MyApp' do use_frameworks! pod 'TruexAdRenderer', '3.15.3' end ``` The second extra `source` is required: CocoaPods looks up a pod's dependencies only in the sources listed in your Podfile, and the dependency is not in the public CocoaPods specs. `TruexAdRenderer` is delivered as a framework, not as a static library, so keep the `use_frameworks!` line in your target. ### Importing The SDK module is `TruexAdRenderer`: ::: code-group ```swift [Swift] import TruexAdRenderer ``` ```objective-c [Objective-C] #import ``` ::: It provides: * `TruexAdRenderer` - renders the ad and is controlled by your app * `TruexAdOptions` - configuration options for the renderer, created with `DefaultOptions()`. Available from Objective-C only - see [Using `TruexAdOptions` from Swift](#using-truexadoptions-from-swift) * `TruexAdRendererDelegate` - the protocol your app implements to receive events, see [Events](./events) ## Renderer Container The renderer presents the ad in its own view controller on top of the view controller you pass to `start`. That view controller should cover the entire screen - for example, the view controller that shows your player. Hide your player controls and any other extra UI while the ad is showing. ## Quick Example A complete start of an Infillion ad, where `adParameters` is the dictionary [resolved from the VAST response](../integration-flow#resolving-adparameters). The Swift version creates the renderer through `TruexRendererFactory`, a small Objective-C helper described in [Using `TruexAdOptions` from Swift](#using-truexadoptions-from-swift): ::: code-group ```swift [Swift] import TruexAdRenderer class PlayerViewController: UIViewController, TruexAdRendererDelegate { private var truexAdRenderer: TruexAdRenderer? private var truexAdCreditReceived = false func startInfillionAd(adParameters: [AnyHashable: Any]) { truexAdCreditReceived = false // Objective-C helper that sets supportsUserCancelStream, see below guard let renderer = TruexRendererFactory.renderer(withAdParameters: adParameters, delegate: self) else { // No renderer means no delegate events: continue the pod finishInfillionAd(skipRemainingAds: false) return } truexAdRenderer = renderer renderer.start(self) } private func disposeRenderer() { truexAdRenderer?.stop() truexAdRenderer = nil } // MARK: - TruexAdRendererDelegate func onAdStarted(_ campaignName: String) { } func onAdFreePod() { // Remember the reward, act on it in onAdCompleted truexAdCreditReceived = true } func onAdCompleted(_ timeSpent: Int) { disposeRenderer() finishInfillionAd(skipRemainingAds: truexAdCreditReceived) } func onAdError(_ errorMessage: String) { disposeRenderer() finishInfillionAd(skipRemainingAds: false) } func onNoAdsAvailable() { disposeRenderer() finishInfillionAd(skipRemainingAds: false) } func onUserCancelStream() { disposeRenderer() exitPlayback() } } ``` ```objective-c [Objective-C] #import @interface PlayerViewController () @property (nonatomic, strong) TruexAdRenderer *truexAdRenderer; @property (nonatomic, assign) BOOL truexAdCreditReceived; @end @implementation PlayerViewController - (void)startInfillionAd:(NSDictionary *)adParameters { self.truexAdCreditReceived = NO; TruexAdOptions options = DefaultOptions(); options.supportsUserCancelStream = YES; self.truexAdRenderer = [[TruexAdRenderer alloc] initWithAdParameters:adParameters options:options delegate:self]; if (!self.truexAdRenderer) { // No renderer means no delegate events: continue the pod [self finishInfillionAd:NO]; return; } [self.truexAdRenderer start:self]; } - (void)disposeRenderer { [self.truexAdRenderer stop]; self.truexAdRenderer = nil; } #pragma mark - TruexAdRendererDelegate - (void)onAdStarted:(NSString *)campaignName { } - (void)onAdFreePod { // Remember the reward, act on it in onAdCompleted: self.truexAdCreditReceived = YES; } - (void)onAdCompleted:(NSInteger)timeSpent { [self disposeRenderer]; [self finishInfillionAd:self.truexAdCreditReceived]; } - (void)onAdError:(NSString *)errorMessage { [self disposeRenderer]; [self finishInfillionAd:NO]; } - (void)onNoAdsAvailable { [self disposeRenderer]; [self finishInfillionAd:NO]; } - (void)onUserCancelStream { [self disposeRenderer]; [self exitPlayback]; } @end ``` ::: `finishInfillionAd` and `exitPlayback` are your app's code - what they have to do is described in [Handling Terminal Events](../integration-flow#handling-terminal-events). The renderer holds its delegate weakly, so keep a strong reference to the renderer while the ad is showing. If the renderer can't be created (`nil`), it will never call the delegate: continue the pod. ## `TruexAdRenderer` Methods ### `initWithAdParameters` ::: code-group ```swift [Swift] // Without options - the variant Swift can call directly init!(adParameters: [AnyHashable: Any]!, delegate: TruexAdRendererDelegate!) ``` ```objective-c [Objective-C] - (id)initWithAdParameters:(NSDictionary *)adParameters options:(TruexAdOptions)options delegate:(id)delegate; ``` ::: Creates the renderer with the `adParameters` dictionary and starts the ad request to the Infillion ad server. The renderer calls [`onFetchAdComplete`](./events#onfetchadcomplete) when the request is done. You can create the renderer early, a few seconds before the ad break starts, to give it more time for the request, and call `start` once the player reaches the ad. The parameters are: * `adParameters`: the JSON payload from the VAST response, see [Resolving `adParameters`](../integration-flow#resolving-adparameters) * `options`: a [`TruexAdOptions`](#truexadoptions) value, created with `DefaultOptions()` * `delegate`: the object that receives the [events](./events) An optional `slotType` (`preroll` or `midroll`) can also be passed with the `initWithAdParameters:slotType:options:delegate:` variant. In Swift the initializer returns an optional (`init!`), and the `options:` variant is not available - see [Using `TruexAdOptions` from Swift](#using-truexadoptions-from-swift). ### `start` ::: code-group ```swift [Swift] func start(_ baseViewController: UIViewController) ``` ```objective-c [Objective-C] - (void)start:(UIViewController *)baseViewController; ``` ::: Shows the ad to the user. Call it when your app is ready to display the ad; it can be called any time after the renderer is created. If the ad request is not finished yet, the renderer waits for it, then presents its view controller on `baseViewController` and calls [`onAdStarted`](./events#onadstarted). After calling `start`, wait for a [terminal event](./events#terminal-events) and keep track of whether [`onAdFreePod`](./events#onadfreepod) has been called. * `baseViewController`: the view controller the renderer presents its view controller on. It should cover the entire screen. ### `stop` ::: code-group ```swift [Swift] func stop() ``` ```objective-c [Objective-C] - (void)stop; ``` ::: Stops the renderer immediately and releases its resources. Call it after a terminal event, and also when: * the user leaves the playback screen while the ad is showing * an unrelated error requires your app to stop the ad * your own timeout for `onFetchAdComplete` or `onAdStarted` is reached A stopped renderer cannot be resumed or reused - remove all references to it. ### `pause` ::: code-group ```swift [Swift] func pause() ``` ```objective-c [Objective-C] - (void)pause; ``` ::: Pauses the ad's videos, audio, and timers. Call it whenever your app needs to pause the ad, for example when it moves to the background. ### `resume` ::: code-group ```swift [Swift] func resume() ``` ```objective-c [Objective-C] - (void)resume; ``` ::: Resumes a paused renderer. ## `TruexAdOptions` Create the options with `DefaultOptions()`, then change the fields you need: | Option | Default | Description | | --- | --- | --- | | `supportsUserCancelStream` | `NO` | If `YES`, closing the ad because the user wants to leave calls [`onUserCancelStream`](./events#onusercancelstream), so your app can exit the stream. If `NO`, back actions call `onOptOut` and `onAdCompleted` instead. | | `userAdvertisingId` | `nil` | The id used for user ad tracking. If not set, the renderer keeps a random id for the installed app. | | `appId` | `nil` | Used for internal tracking. If not set, the renderer uses the app's bundle identifier. | ### Using `TruexAdOptions` from Swift `TruexAdOptions` is a C struct that holds Objective-C objects, and Swift cannot import that kind of struct. As a result, `DefaultOptions()` and the `options:` initializer are not visible from Swift: a Swift app can only call `TruexAdRenderer(adParameters:delegate:)`, which uses the default options - so `onUserCancelStream` is never called. To set the options from a Swift app, add a small Objective-C helper and expose it through your bridging header: ::: code-group ```objective-c [TruexRendererFactory.h] #import NS_ASSUME_NONNULL_BEGIN @interface TruexRendererFactory : NSObject + (nullable TruexAdRenderer *)rendererWithAdParameters:(NSDictionary *)adParameters delegate:(id)delegate; @end NS_ASSUME_NONNULL_END ``` ```objective-c [TruexRendererFactory.m] #import "TruexRendererFactory.h" @implementation TruexRendererFactory + (TruexAdRenderer *)rendererWithAdParameters:(NSDictionary *)adParameters delegate:(id)delegate { TruexAdOptions options = DefaultOptions(); options.supportsUserCancelStream = YES; return [[TruexAdRenderer alloc] initWithAdParameters:adParameters options:options delegate:delegate]; } @end ``` ```objective-c [Bridging header] #import "TruexRendererFactory.h" ``` ```swift [Swift] guard let renderer = TruexRendererFactory.renderer(withAdParameters: adParameters, delegate: self) else { finishInfillionAd(skipRemainingAds: false) return } truexAdRenderer = renderer renderer.start(self) ``` ::: ## App Lifecycle Pause the renderer when your app moves to the background, and resume it when the app becomes active again: ::: code-group ```swift [Swift] override func viewDidLoad() { super.viewDidLoad() NotificationCenter.default.addObserver(self, selector: #selector(appWillResignActive), name: UIApplication.willResignActiveNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(appDidBecomeActive), name: UIApplication.didBecomeActiveNotification, object: nil) } @objc private func appWillResignActive() { truexAdRenderer?.pause() } @objc private func appDidBecomeActive() { truexAdRenderer?.resume() } override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) disposeRenderer() } ``` ```objective-c [Objective-C] - (void)viewDidLoad { [super viewDidLoad]; [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(appWillResignActive) name:UIApplicationWillResignActiveNotification object:nil]; [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(appDidBecomeActive) name:UIApplicationDidBecomeActiveNotification object:nil]; } - (void)appWillResignActive { [self.truexAdRenderer pause]; } - (void)appDidBecomeActive { [self.truexAdRenderer resume]; } - (void)viewDidDisappear:(BOOL)animated { [super viewDidDisappear:animated]; [self disposeRenderer]; } ``` ::: --- --- url: /ads/publisher/platforms/tvos/v3/events.md --- # SDK 3.x Events `TruexAdRenderer` tells your app about the ad lifecycle by calling methods of its `TruexAdRendererDelegate`, declared in `TruexShared.h`. Five methods are required, and the rest are optional. | Method | Required | Terminal | | --- | --- | --- | | [`onFetchAdComplete`](#onfetchadcomplete) | | | | [`onAdStarted`](#onadstarted) | Yes | | | [`onAdCompleted`](#onadcompleted) | Yes | Yes | | [`onAdError`](#onaderror) | Yes | Yes | | [`onNoAdsAvailable`](#onnoadsavailable) | Yes | Yes | | [`onAdFreePod`](#onadfreepod) | Yes | | | [`onUserCancelStream`](#onusercancelstream) | | Yes | | [`onOptIn`](#onoptin) | | | | [`onOptOut`](#onoptout) | | | | [`onSkipCardShown`](#onskipcardshown) | | | | [`onUserCancel`](#onusercancel) | | | ## Event Flow A typical TrueX ad where the user opts in and earns the reward: ``` ◇ onFetchAdComplete │ ↓ ◇ onAdStarted │ ↓ ◇ onOptIn │ ↓ ◆ [User interacts with the ad] │ ↓ ◇ onAdFreePod │ ↓ ◇ onAdCompleted ``` ## Terminal Events A terminal event means the renderer is done, and your app must continue playback - see [Handling Terminal Events](../integration-flow#handling-terminal-events) for what to do in each case: * [`onAdCompleted`](#onadcompleted) - the ad is finished * [`onAdError`](#onaderror) - the renderer hit an unrecoverable error * [`onNoAdsAvailable`](#onnoadsavailable) - there is no ad for this user * [`onUserCancelStream`](#onusercancelstream) - the user wants to exit the stream After a terminal event, call `stop` and remove all references to the renderer instance. Do not resume playback when `onAdFreePod` is called - wait for a terminal event. ## Main Flow Events These events drive the integration and may require action from your app. ### `onFetchAdComplete` ::: code-group ```swift [Swift] func onFetchAdComplete() ``` ```objective-c [Objective-C] - (void)onFetchAdComplete; ``` ::: Optional. Called after the renderer is created, when the ad request is done and the ad is ready to be shown. You can use it for a loading screen or a timeout. For example, create the renderer for a pre-roll and show a loading indicator until this event arrives, then call `start`; if your timeout is reached first, call `stop` and continue with the next ad. For a mid-roll, create the renderer before the slot, and call `start` right away if the event has arrived by the time the player reaches it. ### `onAdStarted` ::: code-group ```swift [Swift] func onAdStarted(_ campaignName: String) ``` ```objective-c [Objective-C] - (void)onAdStarted:(NSString *)campaignName; ``` ::: Required. Called after `start` when the renderer's UI is ready and added to the view hierarchy. Your app does not need to act on it, but you can use it for a timeout: if it has not arrived in a reasonable time after `start`, call `stop` and continue with the next ad. The parameters are: * `campaignName`: the name of the ad campaign shown to the user ### `onAdCompleted` ::: code-group ```swift [Swift] func onAdCompleted(_ timeSpent: Int) ``` ```objective-c [Objective-C] - (void)onAdCompleted:(NSInteger)timeSpent; ``` ::: Required. **Terminal event.** Called when the ad is finished, for example when: * the user chooses the normal video ads, or the choice card countdown runs out * the user completes the TrueX ad and exits it * a TrueX skip card has been shown for its duration The parameters are: * `timeSpent`: time in seconds the user spent in the ad If `onAdFreePod` was called before, skip all remaining ads in the pod. Otherwise, continue with the next ad. ### `onAdError` ::: code-group ```swift [Swift] func onAdError(_ errorMessage: String) ``` ```objective-c [Objective-C] - (void)onAdError:(NSString *)errorMessage; ``` ::: Required. **Terminal event.** Called when the renderer hits an unrecoverable error. Handle it like `onAdCompleted` without `onAdFreePod`. The parameters are: * `errorMessage`: a description of the error ### `onNoAdsAvailable` ::: code-group ```swift [Swift] func onNoAdsAvailable() ``` ```objective-c [Objective-C] - (void)onNoAdsAvailable; ``` ::: Required. **Terminal event.** Called when there is no ad available for this user. Handle it like `onAdCompleted` without `onAdFreePod`. ### `onAdFreePod` ::: code-group ```swift [Swift] func onAdFreePod() ``` ```objective-c [Objective-C] - (void)onAdFreePod; ``` ::: Required. Called when the user has earned the reward. **Do not resume or seek yet** - remember that it was called, and when the terminal `onAdCompleted` arrives, skip all remaining ads in the pod. If it was not called before the terminal event, continue with the next ad so the user gets the normal video ads. ### `onUserCancelStream` ::: code-group ```swift [Swift] func onUserCancelStream() ``` ```objective-c [Objective-C] - (void)onUserCancelStream; ``` ::: Optional. **Terminal event.** Called when the user closes the Infillion ad because they want to exit the stream. Handle it like any other exit from your player - usually by returning the user to the episode or series page. This method is called only if [`supportsUserCancelStream`](./sdk-integration#truexadoptions) is `YES`. Otherwise the renderer calls `onAdCompleted` instead. In a Swift app, set the option through the Objective-C helper described in [Using `TruexAdOptions` from Swift](./sdk-integration#using-truexadoptions-from-swift). ## Informative Events These events are mostly useful for tracking. Your app does not need to act on them, and all of them are optional. ### `onOptIn` ::: code-group ```swift [Swift] func onOptIn(_ campaignName: String, adId: Int) ``` ```objective-c [Objective-C] - (void)onOptIn:(NSString *)campaignName adId:(NSInteger)adId; ``` ::: Called when the user chooses the TrueX interactive ad on the choice card. It can be called more than once if the user opts in, backs out, and opts in again. The parameters are: * `campaignName`: the name of the ad campaign * `adId`: the ad ID ### `onOptOut` ::: code-group ```swift [Swift] func onOptOut(_ userInitiated: Bool) ``` ```objective-c [Objective-C] - (void)onOptOut:(BOOL)userInitiated; ``` ::: Called when the user chooses the normal video ads on the TrueX choice card. The parameters are: * `userInitiated`: `YES` if the user selected it, `NO` if the choice card countdown ran out ### `onSkipCardShown` ::: code-group ```swift [Swift] func onSkipCardShown() ``` ```objective-c [Objective-C] - (void)onSkipCardShown; ``` ::: Called when a skip card is shown, because the user completed a TrueX Sponsored Stream ad earlier in the stream. ### `onUserCancel` ::: code-group ```swift [Swift] func onUserCancel() ``` ```objective-c [Objective-C] - (void)onUserCancel; ``` ::: Called when the user backs out of the TrueX interactive ad after opting in, by confirming the "go back and choose a different ad experience" prompt. The user returns to the choice card with a full countdown, so more `onOptIn` or `onOptOut` calls can follow. --- --- url: /ads/publisher/platforms/tvos/v3/examples.md --- # SDK 3.x Examples Reference apps for `TruexAdRenderer-tvOS` 3.x. They show a TrueX integration in a tvOS app, so you can pick the one closest to your setup and read it from top to bottom. | Example | Language | Description | | --- | --- | --- | | [truex-tvos-reference-app-simple](https://github.com/socialvibe/truex-tvos-reference-app-simple) | Objective-C | A simple tvOS integration of the renderer | | [truex-tvos-reference-app-cocoapods](https://github.com/socialvibe/truex-tvos-reference-app-cocoapods) | Objective-C | The same simple integration, with the renderer added through CocoaPods | | [truex-tvos-google-ad-manager-reference-app](https://github.com/socialvibe/truex-tvos-google-ad-manager-reference-app) | Objective-C | An integration with Google Ad Manager and the IMA SDK | > \[!NOTE] > These apps are maintained separately from the SDK and may use the older `initWithUrl` initializer, which is deprecated. In new code, create the renderer with `initWithAdParameters`, as described in [SDK Integration](./sdk-integration#initwithadparameters). For the flow your app has to implement around the renderer, see [Integration Flow](../integration-flow). --- --- url: /ads/publisher/platforms/tvos/v4/sdk-integration.md --- # SDK 4.x Integration ::: warning Upcoming: not released yet `TruexAdRenderer-tvOS` 4.x is not released yet. This page describes the planned integration, and details such as the final version number and minimum tvOS version will be confirmed with the release. For the current release, see [SDK 3.x Integration](../v3/sdk-integration). ::: SDK 4.x fully supports both [TrueX](../truex) and [IDVx](../idvx) ads. Your app code is the same for both. ## Adding the SDK `TruexAdRenderer-tvOS` 4.x will be distributed through Swift Package Manager and CocoaPods, like 3.x. ### Swift Package Manager 1. In Xcode, go to **File > Add Package Dependencies...** 2. Enter the repository URL: `https://github.com/socialvibe/TruexAdRenderer-tvOS-Swift-Package` 3. Select the `4.x` version, and add the `TruexAdRenderer` product to your app target. ### CocoaPods Add the TrueX spec repository ([socialvibe/cocoapod-specs](https://github.com/socialvibe/cocoapod-specs)) and the spec repository of the renderer's dependency next to the default one, then add the pod to your target: ```ruby source 'https://cdn.cocoapods.org/' source 'https://github.com/socialvibe/cocoapod-specs.git' source 'https://github.com/Innovid/cocoapods-spec.git' target 'MyApp' do use_frameworks! pod 'TruexAdRenderer', '~> 4.0' end ``` ### Importing ::: code-group ```swift [Swift] import TruexAdRendererFramework ``` ```objective-c [Objective-C] @import TruexAdRendererFramework; ``` ::: It provides `TruexAdRenderer`, which renders the ad and is controlled by your app, and the `TruexAdRendererDelegate` protocol, which your app implements to receive [events](./events). ## Quick Example A complete start of an Infillion ad, where `adParameters` is the dictionary [resolved from the VAST response](../integration-flow#resolving-adparameters). It works for TrueX and IDVx ads: ::: code-group ```swift [Swift] import TruexAdRendererFramework class PlayerViewController: UIViewController, TruexAdRendererDelegate { private var truexAdRenderer: TruexAdRenderer? private var truexAdCreditReceived = false func startInfillionAd(adParameters: [String: Any]) { truexAdCreditReceived = false guard let renderer = TruexAdRenderer(adParameters: adParameters as NSDictionary, delegate: self) else { // No renderer means no delegate events: continue the pod finishInfillionAd(skipRemainingAds: false) return } truexAdRenderer = renderer renderer.start(self) } private func disposeRenderer() { truexAdRenderer?.stop() truexAdRenderer = nil } // MARK: - TruexAdRendererDelegate func onAdFreePod() { // Remember the reward, act on it in onAdCompleted truexAdCreditReceived = true } func onAdCompleted(_ timeSpent: TimeInterval) { disposeRenderer() finishInfillionAd(skipRemainingAds: truexAdCreditReceived) } func onAdError(_ errorMessage: String) { disposeRenderer() finishInfillionAd(skipRemainingAds: false) } func onNoAdsAvailable() { disposeRenderer() finishInfillionAd(skipRemainingAds: false) } func onUserCancelStream() { disposeRenderer() exitPlayback() } } ``` ```objective-c [Objective-C] @import TruexAdRendererFramework; @interface PlayerViewController () @property (nonatomic, strong) TruexAdRenderer *truexAdRenderer; @property (nonatomic, assign) BOOL truexAdCreditReceived; @end @implementation PlayerViewController - (void)startInfillionAd:(NSDictionary *)adParameters { self.truexAdCreditReceived = NO; self.truexAdRenderer = [[TruexAdRenderer alloc] initWithAdParameters:adParameters delegate:self]; if (!self.truexAdRenderer) { // No renderer means no delegate events: continue the pod [self finishInfillionAd:NO]; return; } [self.truexAdRenderer start:self]; } - (void)disposeRenderer { [self.truexAdRenderer stop]; self.truexAdRenderer = nil; } #pragma mark - TruexAdRendererDelegate - (void)onAdFreePod { // Remember the reward, act on it in onAdCompleted: self.truexAdCreditReceived = YES; } - (void)onAdCompleted:(NSTimeInterval)timeSpent { [self disposeRenderer]; [self finishInfillionAd:self.truexAdCreditReceived]; } - (void)onAdError:(NSString *)errorMessage { [self disposeRenderer]; [self finishInfillionAd:NO]; } - (void)onNoAdsAvailable { [self disposeRenderer]; [self finishInfillionAd:NO]; } - (void)onUserCancelStream { [self disposeRenderer]; [self exitPlayback]; } @end ``` ::: `finishInfillionAd` and `exitPlayback` are your app's code - what they have to do is described in [Handling Terminal Events](../integration-flow#handling-terminal-events). The renderer holds its delegate weakly, so keep a strong reference to the renderer while the ad is showing. If the renderer can't be created (`nil`), it will never call the delegate: continue the pod. ## `TruexAdRenderer` Methods The renderer is controlled with the same methods as in [SDK 3.x](../v3/sdk-integration#truexadrenderer-methods): | Method | Description | | --- | --- | | `initWithAdParameters:delegate:` | Creates the renderer with the `adParameters` dictionary and starts the ad request. | | `start:` | Shows the ad on the view controller you pass. It should cover the entire screen. After it, wait for a [terminal event](./events#terminal-events). | | `stop` | Stops the renderer and releases its resources. Call it after a terminal event or when your app must end the ad. A stopped renderer cannot be reused. | | `pause` | Pauses the ad's videos, audio, and timers. | | `resume` | Resumes a paused renderer. | ::: code-group ```swift [Swift] init?(adParameters: NSDictionary, delegate: TruexAdRendererDelegate) func start(_ baseViewController: UIViewController) func stop() func pause() func resume() ``` ```objective-c [Objective-C] - (instancetype)initWithAdParameters:(NSDictionary *)adParameters delegate:(id)delegate; - (void)start:(UIViewController *)baseViewController; - (void)stop; - (void)pause; - (void)resume; ``` ::: ## App Lifecycle Pause the renderer when your app moves to the background, and resume it when the app becomes active again: ::: code-group ```swift [Swift] override func viewDidLoad() { super.viewDidLoad() NotificationCenter.default.addObserver(self, selector: #selector(appWillResignActive), name: UIApplication.willResignActiveNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(appDidBecomeActive), name: UIApplication.didBecomeActiveNotification, object: nil) } @objc private func appWillResignActive() { truexAdRenderer?.pause() } @objc private func appDidBecomeActive() { truexAdRenderer?.resume() } override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) disposeRenderer() } ``` ```objective-c [Objective-C] - (void)viewDidLoad { [super viewDidLoad]; [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(appWillResignActive) name:UIApplicationWillResignActiveNotification object:nil]; [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(appDidBecomeActive) name:UIApplicationDidBecomeActiveNotification object:nil]; } - (void)appWillResignActive { [self.truexAdRenderer pause]; } - (void)appDidBecomeActive { [self.truexAdRenderer resume]; } - (void)viewDidDisappear:(BOOL)animated { [super viewDidDisappear:animated]; [self disposeRenderer]; } ``` ::: --- --- url: /ads/publisher/platforms/tvos/v4/events.md --- # SDK 4.x Events ::: warning Upcoming: not released yet `TruexAdRenderer-tvOS` 4.x is not released yet. For the current release, see [SDK 3.x Events](../v3/events). ::: `TruexAdRenderer` tells your app about the ad lifecycle by calling methods of its `TruexAdRendererDelegate`. The methods your app needs for a working integration are: | Method | Terminal | Called for | | --- | --- | --- | | [`onAdCompleted`](#onadcompleted) | Yes | TrueX and IDVx | | [`onAdError`](#onaderror) | Yes | TrueX and IDVx | | [`onNoAdsAvailable`](#onnoadsavailable) | Yes | TrueX and IDVx | | [`onUserCancelStream`](#onusercancelstream) | Yes | TrueX and IDVx | | [`onAdFreePod`](#onadfreepod) | | TrueX only | ## Event Flow A typical TrueX ad where the user opts in and earns the reward: ``` ◆ [User opts in and interacts with the ad] │ ↓ ◇ onAdFreePod │ ↓ ◇ onAdCompleted ``` An IDVx ad has no choice card and no reward, so it ends with `onAdCompleted` and never calls `onAdFreePod`. ## Terminal Events A terminal event means the renderer is done, and your app must continue playback - see [Handling Terminal Events](../integration-flow#handling-terminal-events) for what to do in each case. After a terminal event, call `stop` and remove all references to the renderer instance. ### `onAdCompleted` ::: code-group ```swift [Swift] func onAdCompleted(_ timeSpent: TimeInterval) ``` ```objective-c [Objective-C] - (void)onAdCompleted:(NSTimeInterval)timeSpent; ``` ::: **Terminal event.** Called when the ad is finished, for example when: * the TrueX user chooses the normal video ads, or the choice card countdown runs out * the TrueX user completes the ad and exits it * an IDVx ad ends The parameters are: * `timeSpent`: time in seconds the user spent in the ad If `onAdFreePod` was called before, skip all remaining ads in the pod. Otherwise, continue with the next ad. ### `onAdError` ::: code-group ```swift [Swift] func onAdError(_ errorMessage: String) ``` ```objective-c [Objective-C] - (void)onAdError:(NSString *)errorMessage; ``` ::: **Terminal event.** Called when the renderer hits an unrecoverable error. Handle it like `onAdCompleted` without `onAdFreePod`. ### `onNoAdsAvailable` ::: code-group ```swift [Swift] func onNoAdsAvailable() ``` ```objective-c [Objective-C] - (void)onNoAdsAvailable; ``` ::: **Terminal event.** Called when there is no ad available for this user. Handle it like `onAdCompleted` without `onAdFreePod`. ### `onUserCancelStream` ::: code-group ```swift [Swift] func onUserCancelStream() ``` ```objective-c [Objective-C] - (void)onUserCancelStream; ``` ::: **Terminal event.** Called when the user closes the Infillion ad because they want to exit the stream. Handle it like any other exit from your player - usually by returning the user to the episode or series page. ## Reward Event ### `onAdFreePod` ::: code-group ```swift [Swift] func onAdFreePod() ``` ```objective-c [Objective-C] - (void)onAdFreePod; ``` ::: Called when a TrueX user has earned the reward. **Do not resume or seek yet** - remember that it was called, and when the terminal `onAdCompleted` arrives, skip all remaining ads in the pod. IDVx ads never call this method. --- --- url: /ads/publisher/platforms/tvos/v4/examples.md --- # SDK 4.x Examples ::: warning Upcoming: not released yet `TruexAdRenderer-tvOS` 4.x is not released yet. The reference app below is built against a pre-release build of the SDK. ::: The [truex-tvos-reference-app](https://github.com/socialvibe/truex-tvos-reference-app) repository contains a Swift tvOS app that integrates TrueX and IDVx ads with Google Ad Manager. Each integration is a standalone screen in the app's home menu. | Example | Ad source | Ad break behavior | | --- | --- | --- | | SSAI + Google Ad Manager | A Google IMA DAI VOD stream | Google DAI returns one stream with stitched ad breaks. The app detects the Infillion ad by its `adSystem`, starts `TruexAdRenderer`, and skips or seeks past the ad break when the renderer is done | The app resolves `adParameters` from the generic tag (`traffickingParameters`) first, and from a companion ad second. > \[!NOTE] > The app starts the renderer for every ad whose `adSystem` is `truex` or `idvx`, and does not check the ad's position in the pod. In your app, play a TrueX ad that is not the first ad in the pod as a regular linear ad, as described in [Resolving `adParameters`](../integration-flow#resolving-adparameters). Requirements: Xcode 16.1 or later and tvOS 17 or later. Open `InfillionAdsRefAppTvOS.xcodeproj` in Xcode; Swift Package Manager resolves the dependencies. For the flow your app has to implement around the renderer, see [Integration Flow](../integration-flow). --- --- url: /ads/publisher/platforms/ios/truex.md --- # TrueX ### Product Flow **TrueX** presents users with an **interactive choice card** where they choose between engaging with branded content or watching standard video ads. When users opt in and complete the interactive experience, they earn an **ad credit that skips all remaining ads** in the pod, creating a win-win: viewers get ad-free content, and advertisers get highly engaged audiences. **Key features:** * **Opt-in via choice card** - Users actively choose to engage * **Engagement requirements** - Users must spend 30 seconds and interact at least once to earn credit * **Skips entire ad break** - Successful engagement bypasses all remaining ads in the pod Upon completion, the renderer signals a `adFreePod` event, indicating that remaining linear ads should be skipped. There are two distinct product flows supported by `TruexAdRenderer`: Sponsored Stream (full-stream ad-replacement) and Sponsored Ad Break (mid-roll ad-replacement). In a Sponsored Ad Break flow, once the user hits a mid-roll break with a **TrueX** tag flighted, they will be shown a "choice-card" offering them the choice between watching a normal set of video ads or a fully interactive **TrueX** ad: ![choice card](/assets/images/choice_card.png) ***Fig. A** example **TrueX** mid-roll choice card* If the user opts for a normal ad break, or if the user does not make a selection before the countdown timer expires, the **TrueX** UI will close and playback of normal video ads can continue as usual. If the user opts to interact with **TrueX**, an interactive ad unit will be shown to the user: ![ad](/assets/images/ad.png) ***Fig. B** example **TrueX** interactive ad unit* The requirement for the user to "complete" this ad is for them to spend at least the allotted time on the unit and for at least one interaction (e.g. navigating anywhere through the ad). ![true attention timer example](/assets/images/true-attention-timer-example.png) ***Fig. C** example **TrueX** attention timer* Once the user fulfills both requirements, a "Watch Your Show" button will appear in the bottom right, which the user can select to exit the **TrueX** ad. Having completed a **TrueX** ad, the user will be returned directly to content, skipping the remaining ads in the current ad pod. The Sponsored Stream flow is quite similar. In this scenario, a user will be shown a choice-card in the pre-roll: ![choice card](/assets/images/choice_card.png) ***Fig. D** example **TrueX** pre-roll choice card (full-stream replacement)* Similarly, if the user opts-in and completes the **TrueX** ad, they will be skipped over the remainder of the pre-roll ad break. However, every subsequent mid-roll break in the current stream will also be skipped over. In this case instead of the regular pod of video ads, the user will be shown a "hero card" (also known as a "skip card"): ![skip card](/assets/images/skip_card.png) ***Fig. E** example **TrueX** mid-roll skip card* This messaging will be displayed to the user for several seconds, after which they will be returned directly to content. --- --- url: /ads/publisher/platforms/ios/idvx.md --- # Interactive Video (IDVx) ### How Interactive Video (IDVx) Works **Interactive Video (IDVx)** delivers a new industry standard in video engagement across all devices and platforms. The product goes beyond hovers and inadvertent clicks, to deliver real interaction, by real humans, in premium environments. Built on the same core interactive foundation as TrueX, Interactive Video removes the opt-in, interaction, and time spent requirements, in order to capitalize on the scale of a Publisher's inventory. Infillion pays Publishers a set CPM for the IDVx ads the Publisher renders in their ad pods. **Key features:** * **Automatic start** - Begins playing without opt-in; there is no choice card * **Interactive throughout** - Users can interact with ad content during playback, but no interaction is required * **Capped at 30 seconds** - Unlike the unlimited time a user can spend in a TrueX ad * **Plays inline** - Completes and continues to the next ad in the pod; it does not skip or suppress the Publisher's ad pod ![Interactive Video ad pod flow](/assets/images/image5.jpg) ***Fig. A** example of an IDVx ad playing inline within a regular ad pod* #### User Flow 1. A viewer is streaming video content and reaches a commercial break 2. The content is paused to load the ad pod 3. The IDVx ad is loaded like any other ad, and is one of many in the Publisher's ad pod 4. When the user comes across the IDVx ad, they can interact with it if they choose 5. IDVx ads are 30 seconds long, so when those 30 seconds are up the ad ends 6. The video player then loads either another ad or the video content #### Supported Platforms IDVx ads are available on the following platforms: * [Vega (FireTV)](/platforms/vega/) * [Web/HTML](/platforms/web/) - desktop browsers and HTML5-based CTV apps * [Roku](/platforms/roku/) * [Android](/platforms/android/) - TV & mobile * [iOS](/platforms/ios/) * [tvOS](/platforms/tvos/) - coming soon ## Flighting the IDVx Placement Tag The IDVx placement tag is a 1 stage tag and can be flighted in every ad slot of a pod (except any reserved for TrueX) according to the priority the Publisher wants to give Infillion in filling that slot. For example the Publisher may wish to put the IDVx tag behind direct sold campaign tags, but ahead of programmatic ad tags. Or the Publisher may wish to give the IDVx tag the first position in every last slot of every pod. :::steps 1. The Publisher's AdOps team schedules the IDVx placement tag in any priority they wish throughout their network in their ad server 2. If the Publisher already has the TrueX SDK installed, they need to make a few changes to the integration code ([see below](#changes-existing-truex-publishers-need-to-make-to-support-idvx-ads)) 3. When the ad pod is triggered by the user, the IDVx ad tag will request an ad from the Infillion ad server (we support both CSAI and SSAI) 4. If a match is found for the user, the Infillion ad server will send an IDVx ad response that will trigger the IDVx user flow detailed above 5. If no IDVx ad is matched for the user by the Infillion ad server, an empty VAST response will be passed back, triggering whatever is scheduled next - either another video ad or the content ::: ## Changes existing TrueX Publishers need to make to support IDVx ads If your iOS app already renders TrueX ads: 1. Allow the renderer to run for ads with `` `IDVx` in any pod position. The "first ad in the pod" rule applies to TrueX only. 2. Initialize the renderer with the `adParameters` JSON from the VAST response, using `initWithAdParameters` - see [SDK Integration](./sdk-integration#initwithadparameters). 3. After an IDVx ad finishes, always continue with the next ad in the pod. IDVx never calls `onAdFreePod` - see [Integration Flow](./integration-flow#handling-terminal-events). ## Creative Example This animated gif illustrates the interactivity of an IDVx ad creative. ![Interactive Video creative example](/assets/images/image3.gif) > \[!TIP] > The [Infillion Engagement Showcase](https://engagements.infillion.com/showcase/) shows working examples of TrueX and IDVx ads on all supported devices and platforms. If the ad experience does not load right away in the demo environment, refresh the page and it should load properly. --- --- url: /ads/publisher/platforms/ios.md --- # iOS Integration The TrueX Ad Renderer (TAR) for iOS renders Infillion interactive ads - [TrueX](./truex) and [IDVx](./idvx) - inside native iPhone and iPad apps that play long-form video with ad breaks, known as FEP (Full Episode Player) apps. ## Supported Devices The `TruexAdRenderer-iOS` SDK supports: * iPhone * iPad For Apple TV, see the [tvOS SDK](../tvos/). ## TrueX and IDVx Both ad products are requested with a VAST tag in one of two formats, generic or companion - see [VAST Tag Types](./vast-tags). The same `TruexAdRenderer` renders both, and your app tells them apart by the VAST `` value: | | [TrueX](./truex) | [IDVx](./idvx) | | --- | --- | --- | | `` | `trueX` | `IDVx` | | Entry | Opt-in through a choice card | Starts automatically | | Pod position | First ad in the pod only | Any slot | | Reward | `onAdFreePod` - the remaining ads in the pod are skipped | None - the pod continues | ## What You'll Need * An iOS app with a video player and an ad integration (Google IMA DAI, Google IMA CSAI, or your own ad layer) * iOS 15.6 or later * `TruexAdRenderer-iOS` `4.1.0` - see [SDK Integration](./sdk-integration) * A full-screen `UIView` above your video player where the renderer can show the interactive ad * A test VAST tag from your Infillion contact - see [Integration Flow](./integration-flow) ## Next Steps * [Integration Flow](./integration-flow) - what Infillion provides, and what your app has to build * [SDK Integration](./sdk-integration) - adding the SDK and calling `TruexAdRenderer` * [Events](./events) - every `TruexAdRendererDelegate` method * [Examples](./examples) - runnable Swift and Objective-C reference apps for Manual CSAI, Google IMA CSAI, and Google IMA SSAI --- --- url: /ads/publisher/platforms/ios/vast-tags.md --- # VAST Tag Types Infillion uses two types of VAST tags for both TrueX and Interactive Video products. The tags differ in how the `adParameters` JSON payload is embedded in the VAST XML. ### "Generic" Tag The `adParameters` are embedded directly as a JSON string within the `` node: **TrueX "Generic" Tag:** ``` https://get.truex.com/{placement_hash}/vast/generic?{ad_request_parameters} ``` **Interactive Video "Generic" Tag:** ``` https://get.truex.com/{placement_hash}/vast/idvx/generic?{ad_request_parameters} ``` **VAST Structure:** ```xml trueX 00:00:30 ``` ### "Companion" Tag The `adParameters` are embedded in a companion ad's `` node as base64-encoded json string: **TrueX "Companion" Tag:** ``` https://get.truex.com/{placement_hash}/vast/companion?{ad_request_parameters} ``` **IDVx "Companion" Tag:** ``` https://get.truex.com/{placement_hash}/vast/idvx/companion?{ad_request_parameters} ``` **VAST Structure:** ```xml trueX 00:00:30 ``` ### Differentiating TrueX and IDVx The `` node value identifies the ad type: * **TrueX ads**: `trueX` * **IDVx ads**: `IDVx` Both tag types (Generic and Companion) work with both TrueX and IDVx products. --- --- url: /ads/publisher/platforms/ios/integration-flow.md --- # Integration Flow An iOS 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. :::steps 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](./vast-tags). 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 can be placed in any slot. 3. The publisher integrates the SDK Your app [resolves `adParameters`](#resolving-adparameters) from the ad, [starts the renderer](#starting-the-renderer) when the ad plays, and [handles the terminal events](#handling-terminal-events). ::: ## Resolving `adParameters` The renderer is initialized with an `adParameters` dictionary. Where that JSON comes from depends on the VAST tag format and the ad framework your app uses: | Tag format | Where `adParameters` is in the VAST | Google IMA (CSAI and DAI) | Custom VAST parsing | | --- | --- | --- | --- | | Generic | `` as a JSON string | `ad.traffickingParameters` | Text content of `` | | Companion | `` as a base64 JSON `data:` URL | Use the generic tag with Google IMA | Text content of ``, base64-decoded after the comma | First, identify the ad by its `` value (`ad.adSystem` in Google IMA), compared case-insensitively. `trueX` is a TrueX ad and `IDVx` is an IDVx 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. 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 `` 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 companion decoding, adapted from the [examples](./examples#manual-csai) (`ManualVastPayload`): ::: code-group ```swift [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 `` (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 [Objective-C] // Parses `` (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. + (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`). The [examples](./examples) contain the complete parsers, including the VAST XML parsing for a custom ad layer. ## Starting the Renderer When the Infillion ad starts playing: 1. Pause the content player and the ad playback (for Google IMA CSAI, also pause the `IMAAdsManager`). 2. Prepare to move past the placeholder ad video: * CSAI and custom ad layers: seek the placeholder to just before its end, so it finishes right away when the ad playback resumes. * SSAI: the placeholder is stitched into the stream, so remember where it ends (the current stream time plus `ad.duration`) to seek there later. 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 above your player. 5. Wait for a terminal event. While you wait, keep track of whether `onAdFreePod` has been called. The calls are described in [SDK Integration](./sdk-integration). ## 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 result | What to do | Google IMA CSAI | Google IMA DAI (SSAI) | Custom ad layer | | --- | --- | --- | --- | --- | | `onAdCompleted` after `onAdFreePod` | Skip all remaining ads in the pod and continue the content | `[adsManager discardAdBreak]`, then play the content | Seek past the end of the current ad break's cue point | End the pod and restore the content | | `onAdCompleted` without `onAdFreePod` | Skip the Infillion placeholder ad and play the next ad in the pod | `[adsManager resume]` - the placeholder finishes and IMA plays the next ad | Seek to just before the placeholder's end, so the next stitched ad plays | Play the next ad in the pod | | `onAdError` or `onNoAdsAvailable` | Same as `onAdCompleted` without `onAdFreePod` | Same | Same | Same | | `onUserCancelStream` | The user closed the Infillion ad because they want to leave - exit the playback screen | Close the player | Close the player | Close the player | `onAdCompleted` without `onAdFreePod` covers a TrueX user who opted out or let the choice card time out, and it is 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](./events#terminal-events) page. --- --- url: /ads/publisher/platforms/ios/sdk-integration.md --- # SDK Integration ## Adding the SDK `TruexAdRenderer-iOS` is distributed through Swift Package Manager (recommended) and CocoaPods. The current version is `4.1.0`, and it requires iOS 15.6 or later. ### Swift Package Manager 1. In Xcode, go to **File > Add Package Dependencies...** 2. Enter the repository URL: `https://github.com/socialvibe/TruexAdRenderer-iOS-Swift-Package` 3. Select version `4.1.0` or later, and add the `TruexAdRenderer-iOS` product to your app target. Or, in a `Package.swift` manifest: ```swift dependencies: [ .package(url: "https://github.com/socialvibe/TruexAdRenderer-iOS-Swift-Package", from: "4.1.0"), ], targets: [ .target( name: "MyApp", dependencies: [ .product(name: "TruexAdRenderer-iOS", package: "TruexAdRenderer-iOS-Swift-Package"), ] ), ] ``` ### CocoaPods Add the TrueX spec repository next to the default one, then add the pod to your target: ```ruby source 'https://cdn.cocoapods.org/' source 'https://github.com/socialvibe/cocoapod-specs.git' target 'MyApp' do use_frameworks! pod 'TruexAdRenderer-iOS', '4.1.0' end ``` ### Importing The SDK module is `TruexAdRenderer`: ::: code-group ```swift [Swift] import TruexAdRenderer ``` ```objective-c [Objective-C] #import ``` ::: It provides: * `TruexAdRenderer` - renders the ad and is controlled by your app * `TruexAdOptions` - configuration options for the renderer, created with `DefaultOptions()`. Available from Objective-C only - see [Using `TruexAdOptions` from Swift](#using-truexadoptions-from-swift) * `TruexAdRendererDelegate` - the protocol your app implements to receive events, see [Events](./events) ## Renderer Container The renderer shows the ad in a `UIView` that you provide. It should cover the entire screen and sit above your video player - for example, the root view of your player view controller. Hide as much other UI as possible while the ad is showing, including player controls, the status bar, and the home indicator: ::: code-group ```swift [Swift] override var prefersStatusBarHidden: Bool { return truexAdRenderer != nil } override var prefersHomeIndicatorAutoHidden: Bool { return truexAdRenderer != nil } ``` ```objective-c [Objective-C] - (BOOL)prefersStatusBarHidden { return self.truexAdRenderer != nil; } - (BOOL)prefersHomeIndicatorAutoHidden { return self.truexAdRenderer != nil; } ``` ::: ## Quick Example A complete start of an Infillion ad, where `adParameters` is the dictionary [resolved from the VAST response](./integration-flow#resolving-adparameters). The Swift version creates the renderer through `TruexRendererFactory`, a small Objective-C helper described in [Using `TruexAdOptions` from Swift](#using-truexadoptions-from-swift): ::: code-group ```swift [Swift] import TruexAdRenderer class PlayerViewController: UIViewController, TruexAdRendererDelegate { private var truexAdRenderer: TruexAdRenderer? private var truexAdCreditReceived = false func startInfillionAd(adParameters: [AnyHashable: Any]) { truexAdCreditReceived = false // Objective-C helper that sets supportsUserCancelStream, see below truexAdRenderer = TruexRendererFactory.renderer(withAdParameters: adParameters, delegate: self) truexAdRenderer?.start(view) } private func disposeRenderer() { truexAdRenderer?.stop() truexAdRenderer = nil } // MARK: - TruexAdRendererDelegate func onAdFreePod() { // Remember the reward, act on it in onAdCompleted truexAdCreditReceived = true } func onAdCompleted(_ timeSpent: Int) { disposeRenderer() finishInfillionAd(skipRemainingAds: truexAdCreditReceived) } func onAdError(_ errorMessage: String) { disposeRenderer() finishInfillionAd(skipRemainingAds: false) } func onNoAdsAvailable() { disposeRenderer() finishInfillionAd(skipRemainingAds: false) } func onUserCancelStream() { disposeRenderer() exitPlayback() } func onPopupWebsite(_ url: String) { // See the onPopupWebsite event } } ``` ```objective-c [Objective-C] #import @interface PlayerViewController () @property (nonatomic, strong) TruexAdRenderer *truexAdRenderer; @property (nonatomic, assign) BOOL truexAdCreditReceived; @end @implementation PlayerViewController - (void)startInfillionAd:(NSDictionary *)adParameters { self.truexAdCreditReceived = NO; TruexAdOptions options = DefaultOptions(); options.supportsUserCancelStream = YES; self.truexAdRenderer = [[TruexAdRenderer alloc] initWithAdParameters:adParameters options:options delegate:self]; [self.truexAdRenderer start:self.view]; } - (void)disposeRenderer { [self.truexAdRenderer stop]; self.truexAdRenderer = nil; } #pragma mark - TruexAdRendererDelegate - (void)onAdFreePod { // Remember the reward, act on it in onAdCompleted: self.truexAdCreditReceived = YES; } - (void)onAdCompleted:(NSInteger)timeSpent { [self disposeRenderer]; [self finishInfillionAd:self.truexAdCreditReceived]; } - (void)onAdError:(NSString *)errorMessage { [self disposeRenderer]; [self finishInfillionAd:NO]; } - (void)onNoAdsAvailable { [self disposeRenderer]; [self finishInfillionAd:NO]; } - (void)onUserCancelStream { [self disposeRenderer]; [self exitPlayback]; } - (void)onPopupWebsite:(NSString *)url { // See the onPopupWebsite: event } @end ``` ::: `finishInfillionAd` and `exitPlayback` are your app's code - what they have to do is described in [Handling Terminal Events](./integration-flow#handling-terminal-events). The renderer holds its delegate weakly, so keep a strong reference to the renderer while the ad is showing. If the renderer can't be created (`nil`), it will never call the delegate: continue the pod. ## `TruexAdRenderer` Methods ### `initWithAdParameters` ::: code-group ```swift [Swift] // Without options - the only variant Swift can call directly init!(adParameters: [AnyHashable: Any]!, delegate: TruexAdRendererDelegate!) ``` ```objective-c [Objective-C] - (id)initWithAdParameters:(NSDictionary *)adParameters options:(TruexAdOptions)options delegate:(id)delegate; ``` ::: Creates the renderer with the `adParameters` dictionary and starts the ad request to the Infillion ad server. The renderer calls [`onAdFetchCompleted`](./events#onadfetchcompleted) when the request is done. You can create the renderer early, a few seconds before the ad break starts, to give it more time for the request, and call `start` once the player reaches the ad. The parameters are: * `adParameters`: the JSON payload from the VAST response, see [Resolving `adParameters`](./integration-flow#resolving-adparameters) * `options`: a [`TruexAdOptions`](#truexadoptions) value, created with `DefaultOptions()` * `delegate`: the object that receives the [events](./events) In Swift the initializer returns an optional (`init!`), and the `options:` variant is not available - see [Using `TruexAdOptions` from Swift](#using-truexadoptions-from-swift). ### `start` ::: code-group ```swift [Swift] func start(_ baseView: UIView) ``` ```objective-c [Objective-C] - (void)start:(UIView *)baseView; ``` ::: Shows the ad to the user. Call it when your app is ready to display the ad; it can be called any time after the renderer is created. If the ad request is not finished yet, the renderer waits for it, then adds its view to `baseView` and calls [`onAdStarted`](./events#onadstarted). After calling `start`, wait for a [terminal event](./events#terminal-events) and keep track of whether [`onAdFreePod`](./events#onadfreepod) has been called. * `baseView`: the view the renderer adds its view to. It should cover the entire screen. ### `stop` ::: code-group ```swift [Swift] func stop() ``` ```objective-c [Objective-C] - (void)stop; ``` ::: Stops the renderer immediately and releases its resources. Call it after a terminal event, and also when: * the user leaves the playback screen while the ad is showing * an unrelated error requires your app to stop the ad * your own timeout for `onAdFetchCompleted` or `onAdStarted` is reached A stopped renderer cannot be resumed or reused - remove all references to it. ### `pause` ::: code-group ```swift [Swift] func pause() ``` ```objective-c [Objective-C] - (void)pause; ``` ::: Pauses the ad's videos, audio, and timers. Call it when your app moves to the background, and when your app shows a web page for [`onPopupWebsite`](./events#onpopupwebsite). ### `resume` ::: code-group ```swift [Swift] func resume() ``` ```objective-c [Objective-C] - (void)resume; ``` ::: Resumes a paused renderer. ## `TruexAdOptions` Create the options with `DefaultOptions()`, then change the fields you need: | Option | Default | Description | | --- | --- | --- | | `supportsUserCancelStream` | `NO` | If `YES`, closing the ad because the user wants to leave calls [`onUserCancelStream`](./events#onusercancelstream), so your app can exit the stream. Set it to `YES` for both TrueX and IDVx ads. | | `appId` | `nil` | Used for internal tracking. If not set, the renderer uses the app's bundle identifier. | | `enableWebViewDebugging` | `NO` | If `YES`, the ad's web view can be inspected with Safari Web Inspector. Enable it in debug builds only. | ### Using `TruexAdOptions` from Swift `TruexAdOptions` is a C struct that holds Objective-C objects, and Swift cannot import that kind of struct. As a result, `DefaultOptions()` and the `options:` initializer are not visible from Swift: a Swift app can only call `TruexAdRenderer(adParameters:delegate:)`, which uses the default options - so `onUserCancelStream` is never called. To set the options from a Swift app, add a small Objective-C helper and expose it through your bridging header. The Swift app in the [examples](./examples) uses the same helper: ::: code-group ```objective-c [TruexRendererFactory.h] #import NS_ASSUME_NONNULL_BEGIN @interface TruexRendererFactory : NSObject + (nullable TruexAdRenderer *)rendererWithAdParameters:(NSDictionary *)adParameters delegate:(id)delegate; @end NS_ASSUME_NONNULL_END ``` ```objective-c [TruexRendererFactory.m] #import "TruexRendererFactory.h" @implementation TruexRendererFactory + (TruexAdRenderer *)rendererWithAdParameters:(NSDictionary *)adParameters delegate:(id)delegate { TruexAdOptions options = DefaultOptions(); options.supportsUserCancelStream = YES; #if DEBUG options.enableWebViewDebugging = YES; #endif return [[TruexAdRenderer alloc] initWithAdParameters:adParameters options:options delegate:delegate]; } @end ``` ```objective-c [Bridging header] #import "TruexRendererFactory.h" ``` ```swift [Swift] truexAdRenderer = TruexRendererFactory.renderer(withAdParameters: adParameters, delegate: self) truexAdRenderer?.start(view) ``` ::: ## App Lifecycle Pause the renderer when your app moves to the background, and resume it when the app becomes active again: ::: code-group ```swift [Swift] override func viewDidLoad() { super.viewDidLoad() NotificationCenter.default.addObserver(self, selector: #selector(appWillResignActive), name: UIApplication.willResignActiveNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(appDidBecomeActive), name: UIApplication.didBecomeActiveNotification, object: nil) } @objc private func appWillResignActive() { truexAdRenderer?.pause() } @objc private func appDidBecomeActive() { truexAdRenderer?.resume() } override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) disposeRenderer() } ``` ```objective-c [Objective-C] - (void)viewDidLoad { [super viewDidLoad]; [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(appWillResignActive) name:UIApplicationWillResignActiveNotification object:nil]; [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(appDidBecomeActive) name:UIApplicationDidBecomeActiveNotification object:nil]; } - (void)appWillResignActive { [self.truexAdRenderer pause]; } - (void)appDidBecomeActive { [self.truexAdRenderer resume]; } - (void)viewDidDisappear:(BOOL)animated { [super viewDidDisappear:animated]; [self disposeRenderer]; } ``` ::: --- --- url: /ads/publisher/platforms/ios/events.md --- # Events `TruexAdRenderer` tells your app about the ad lifecycle by calling methods of its `TruexAdRendererDelegate`. Five methods are required, and the rest are optional. | Method | Required | Terminal | | --- | --- | --- | | [`onAdFetchCompleted`](#onadfetchcompleted) | | | | [`onAdStarted`](#onadstarted) | | | | [`onAdDisplayed`](#onaddisplayed) | | | | [`onAdCompleted`](#onadcompleted) | Yes | Yes | | [`onAdError`](#onaderror) | Yes | Yes | | [`onNoAdsAvailable`](#onnoadsavailable) | Yes | Yes | | [`onAdFreePod`](#onadfreepod) | Yes | | | [`onPopupWebsite`](#onpopupwebsite) | Yes | | | [`onUserCancelStream`](#onusercancelstream) | | Yes | | [`onOptIn`](#onoptin) | | | | [`onOptOut`](#onoptout) | | | | [`onSkipCardShown`](#onskipcardshown) | | | | [`onUserCancel`](#onusercancel) | | | ## Event Flow A typical TrueX ad where the user opts in and earns the reward: ``` ◇ onAdFetchCompleted │ ↓ ◇ onAdStarted │ ↓ ◇ onAdDisplayed │ ↓ ◇ onOptIn │ ↓ ◆ [User interacts with the ad] │ ↓ ◇ onAdFreePod │ ↓ ◇ onAdCompleted: ``` An IDVx ad has no choice card and no reward, so it goes from `onAdDisplayed` straight to `onAdCompleted`, without `onOptIn`, `onOptOut`, or `onAdFreePod`. ## Terminal Events A terminal event means the renderer is done, and your app must continue playback - see [Handling Terminal Events](./integration-flow#handling-terminal-events) for what to do in each case: * [`onAdCompleted`](#onadcompleted) - the ad is finished * [`onAdError`](#onaderror) - the renderer hit an unrecoverable error * [`onNoAdsAvailable`](#onnoadsavailable) - there is no ad for this user * [`onUserCancelStream`](#onusercancelstream) - the user wants to exit the stream After a terminal event, call `stop` and remove all references to the renderer instance. ## Main Flow Events These events drive the integration and may require action from your app. ### `onAdFetchCompleted` ::: code-group ```swift [Swift] func onAdFetchCompleted() ``` ```objective-c [Objective-C] - (void)onAdFetchCompleted; ``` ::: Optional. Called after the renderer is created, when the ad request is done and the ad is ready to be shown. You can use it for a loading screen or a timeout. For example, create the renderer before a midroll and show the ad with `start` once this event arrives; if your timeout is reached first, call `stop` and continue with the next ad. ### `onAdStarted` ::: code-group ```swift [Swift] func onAdStarted() ``` ```objective-c [Objective-C] - (void)onAdStarted; ``` ::: Optional. Called after `start` when the renderer's view is added to the view hierarchy. ### `onAdDisplayed` ::: code-group ```swift [Swift] func onAdDisplayed() ``` ```objective-c [Objective-C] - (void)onAdDisplayed; ``` ::: Optional. Called after `onAdStarted` when the initial ad assets are loaded and visible on the screen. This is useful only if your app shows its own loading screen instead of the renderer's loading spinner: hide your loading screen when this event arrives. ### `onAdCompleted` ::: code-group ```swift [Swift] func onAdCompleted(_ timeSpent: Int) ``` ```objective-c [Objective-C] - (void)onAdCompleted:(NSInteger)timeSpent; ``` ::: Required. **Terminal event.** Called when the ad is finished, for example when: * the TrueX user chooses the normal video ads, or the choice card countdown runs out * the TrueX user completes the ad and exits it * a TrueX skip card has been shown for its duration * an IDVx ad ends The parameters are: * `timeSpent`: time in seconds the user spent in the ad If `onAdFreePod` was called before, skip all remaining ads in the pod. Otherwise, continue with the next ad. ### `onAdError` ::: code-group ```swift [Swift] func onAdError(_ errorMessage: String) ``` ```objective-c [Objective-C] - (void)onAdError:(NSString *)errorMessage; ``` ::: Required. **Terminal event.** Called when the renderer hits an unrecoverable error. Handle it like `onAdCompleted` without `onAdFreePod`. The parameters are: * `errorMessage`: a description of the error ### `onNoAdsAvailable` ::: code-group ```swift [Swift] func onNoAdsAvailable() ``` ```objective-c [Objective-C] - (void)onNoAdsAvailable; ``` ::: Required. **Terminal event.** Called when there is no ad available for this user. Handle it like `onAdCompleted` without `onAdFreePod`. ### `onAdFreePod` ::: code-group ```swift [Swift] func onAdFreePod() ``` ```objective-c [Objective-C] - (void)onAdFreePod; ``` ::: Required. Called when a TrueX user has earned the reward. **Do not resume or seek yet** - remember that it was called, and when the terminal `onAdCompleted` arrives, skip all remaining ads in the pod. IDVx ads never call this method. ### `onPopupWebsite` ::: code-group ```swift [Swift] func onPopupWebsite(_ url: String) { guard let pageUrl = URL(string: url) else { return } truexAdRenderer?.pause() let safari = SFSafariViewController(url: pageUrl) safari.delegate = self present(safari, animated: true) } // SFSafariViewControllerDelegate func safariViewControllerDidFinish(_ controller: SFSafariViewController) { truexAdRenderer?.resume() } ``` ```objective-c [Objective-C] - (void)onPopupWebsite:(NSString *)url { [self.truexAdRenderer pause]; SFSafariViewController *safari = [[SFSafariViewController alloc] initWithURL:[NSURL URLWithString:url]]; safari.delegate = self; [self presentViewController:safari animated:YES completion:nil]; } // SFSafariViewControllerDelegate - (void)safariViewControllerDidFinish:(SFSafariViewController *)controller { [self.truexAdRenderer resume]; } ``` ::: Required. Called when the user taps an external link in the ad. Call `pause` on the renderer and show the page - in an `SFSafariViewController`, an in-app web view, or Safari. When the user comes back, call `resume`. The parameters are: * `url`: the URL to open ### `onUserCancelStream` ::: code-group ```swift [Swift] func onUserCancelStream() ``` ```objective-c [Objective-C] - (void)onUserCancelStream; ``` ::: Optional. **Terminal event.** Called when the user closes the Infillion ad because they want to exit the stream. Handle it like any other exit from your player - usually by returning the user to the episode or series page. This method is called only if [`supportsUserCancelStream`](./sdk-integration#truexadoptions) is `YES`. In a Swift app, set it through the Objective-C helper described in [Using `TruexAdOptions` from Swift](./sdk-integration#using-truexadoptions-from-swift). ## Informative Events These events are mostly useful for tracking. Your app does not need to act on them, and all of them are optional. ### `onOptIn` ::: code-group ```swift [Swift] func onOptIn() ``` ```objective-c [Objective-C] - (void)onOptIn; ``` ::: Called when the user chooses the TrueX interactive ad on the choice card. It can be called more than once if the user opts in, backs out, and opts in again. ### `onOptOut` ::: code-group ```swift [Swift] func onOptOut(_ userInitiated: Bool) ``` ```objective-c [Objective-C] - (void)onOptOut:(BOOL)userInitiated; ``` ::: Called when the user chooses the normal video ads on the TrueX choice card. The parameters are: * `userInitiated`: `YES` if the user selected it, `NO` if the choice card countdown ran out ### `onSkipCardShown` ::: code-group ```swift [Swift] func onSkipCardShown() ``` ```objective-c [Objective-C] - (void)onSkipCardShown; ``` ::: Called when a skip card is shown, because the user completed a TrueX Sponsored Stream ad earlier in the stream. ### `onUserCancel` ::: code-group ```swift [Swift] func onUserCancel() ``` ```objective-c [Objective-C] - (void)onUserCancel; ``` ::: Called when the user backs out of the TrueX interactive ad after opting in, by confirming the "go back and choose a different ad experience" prompt. The user returns to the choice card with a full countdown, so more `onOptIn` or `onOptOut` calls can follow. ## Deprecated Methods Older integrations and reference apps may still implement these methods. They are still declared in the SDK but marked as deprecated - implement the replacements in new code: | Deprecated | Use instead | | --- | --- | | `onAdStarted:` (with `campaignName`) | [`onAdStarted`](#onadstarted) | | `onOptIn:adId:` (with `campaignName` and `adId`) | [`onOptIn`](#onoptin) | | `onAdsAvailable` | [`onAdStarted`](#onadstarted) | --- --- url: /ads/publisher/platforms/ios/examples.md --- # Examples The [infillion-ads-ios-examples][repo] repository contains two iPhone and iPad apps with the same three complete integrations of TrueX and IDVx ads into an `AVPlayer` app - one in Swift (`swift-ios-app`) and one in Objective-C (`objc-ios-app`): | Example | Ad source | Ad break behavior | | --- | --- | --- | | [Manual CSAI](#manual-csai) | VAST tags from a bundled JSON ad break | The app schedules and plays the ad pod itself | | [Google IMA CSAI](#google-ima-csai) | A bundled VMAP | Google IMA requests and sequences client-side ads | | [Google IMA SSAI](#google-ima-ssai) | A Google DAI VOD stream | Google DAI returns one stream with stitched ad breaks | Each example has its own player, ad, and `TruexAdRenderer` code on purpose, so you can pick the one closest to your setup and read it from top to bottom. Both apps have the same examples, flows, and sample configuration; only the language differs. The Swift app creates the renderer through the small Objective-C `TruexRendererFactory` - see [Using `TruexAdOptions` from Swift](./sdk-integration#using-truexadoptions-from-swift). ## Running the Apps Requirements: * Xcode 16 or later * An iOS simulator or device running iOS 15.6 or later Open `swift-ios-app/InfillionAdsExamples.xcodeproj` (Swift) or `objc-ios-app/InfillionAdsExamplesObjC.xcodeproj` (Objective-C). Xcode resolves the Google IMA SDK and `TruexAdRenderer-iOS` through Swift Package Manager. Run the `InfillionAdsExamples` or `InfillionAdsExamplesObjC` scheme on an iPhone simulator and pick an example. The two apps have different bundle IDs, so both can be installed side by side. Each example shows its current state (content, linear ad, interactive ad, errors) in a status panel, and logs it with the `[InfillionAdsExamples]` prefix. > \[!NOTE] Test IP parameter > The sample VAST tags include `&ip=158.106.195.210`, the IP of the Infillion NYC office. The TrueX ad server serves ads in the US and Canada only, and this parameter lets teams and CI runners outside those regions get filled test ads. Do not use it in production. ## Shared Rules All three examples follow the same rules, implemented as small functions you can copy: * An ad is an Infillion ad if its `` is `trueX` or `IDVx`, compared case-insensitively. * TrueX is interactive only as the first ad in the pod; at a later position it plays as a regular linear ad. IDVx is interactive in any position. * `adParameters` comes from the ad (see [Resolving `adParameters`](./integration-flow#resolving-adparameters)). If it is missing or invalid, or the renderer can't be created, the renderer is not started and the pod continues. * Only a TrueX ad that called `onAdFreePod` skips the rest of the pod after `onAdCompleted`. * `onUserCancelStream` closes the player screen. `onPopupWebsite` pauses the renderer and opens `SFSafariViewController`, and the renderer resumes when Safari closes. ::: code-group ```swift [Swift] /// TrueX is interactive only as the first ad in a pod; IDVx in any position. func canPlayImaCsaiInteractive(_ type: ImaCsaiAdType, adPosition: Int) -> Bool { switch type { case .truex: return adPosition == 1 case .idvx: return true case .linear: return false } } /// Only a TrueX ad that earned credit (onAdFreePod) skips the rest of the pod. func shouldDiscardImaCsaiAdBreak(_ type: ImaCsaiAdType, earnedCredit: Bool) -> Bool { type == .truex && earnedCredit } ``` ```objective-c [Objective-C] // TrueX is interactive only as the first ad in a pod; IDVx in any position. FOUNDATION_EXTERN BOOL CanPlayImaCsaiInteractive(ImaCsaiAdType type, NSInteger adPosition); // Only a TrueX ad that earned credit (onAdFreePod) skips the rest of the pod. FOUNDATION_EXTERN BOOL ShouldDiscardImaCsaiAdBreak(ImaCsaiAdType type, BOOL earnedCredit); ``` ::: The Objective-C app has the same rules as C functions with capitalized names (`ClassifyImaCsaiAd`, `CanPlayImaCsaiInteractive`, `ImaSsaiAdParameters`, ...), covered by the same unit tests. ## Manual CSAI Use this example when your app or your own ad layer decides when an ad break starts and which ads it contains. 1. The app loads the ad break from [`manual_ad_break.json`][manual_fixture] and plays content. A time observer detects the midroll at 10 seconds. 2. At the midroll, the app saves the content position, pauses content, and fetches the VAST tag of every Infillion ad in the pod. It resolves `adParameters` from each response - the `truex` companion first, then ``. Fetching at break start gives each break a fresh ad session. 3. The pod plays ad by ad in the same `AVPlayer`. For an Infillion ad that can play interactively, the app starts `TruexAdRenderer` over the player instead of playing the placeholder video. 4. On `onAdCompleted` after `onAdFreePod`, the app ends the pod and restores the content. Otherwise, and on `onAdError` or `onNoAdsAvailable`, it plays the next ad. The sample fixture uses the **companion** tag for TrueX and the **generic** tag for IDVx, so one ad break exercises both formats. ::: code-group ```swift [Swift] private func playAd(at index: Int) { guard let adBreak, index < adBreak.ads.count else { endAdBreak() return } currentAdIndex = index let ad = adBreak.ads[index] let position = index + 1 if canPlayInteractive(ad.type, adPosition: position) { if let adParameters = adParametersByIndex[index] { startInteractiveAd(ad, adParameters: adParameters, position: position) } else { // No usable adParameters: don't start the renderer, continue the pod. playAd(at: index + 1) } } else { playLinearAd(ad, position: position) } } private func finishInteractiveAd(skipRemainingAds: Bool) { disposeRenderer() if skipRemainingAds { // TrueX credit earned: skip the rest of the pod endAdBreak() } else { playAd(at: currentAdIndex + 1) } } ``` ```objective-c [Objective-C] - (void)playAdAtIndex:(NSInteger)index { if (index >= (NSInteger)_adBreak.ads.count) { [self endAdBreak]; return; } _currentAdIndex = index; ManualAd *ad = _adBreak.ads[(NSUInteger)index]; NSInteger position = index + 1; if (CanPlayInteractive(ad.type, position)) { NSDictionary *adParameters = _adParametersByIndex[@(index)]; if (adParameters) { [self startInteractiveAd:ad adParameters:adParameters position:position]; } else { // No usable adParameters: don't start the renderer, continue the pod. [self playAdAtIndex:index + 1]; } } else { [self playLinearAd:ad position:position]; } } - (void)finishInteractiveAdSkippingRemainingAds:(BOOL)skipRemainingAds { [self disposeRenderer]; if (skipRemainingAds) { // TrueX credit earned: skip the rest of the pod [self endAdBreak]; } else { [self playAdAtIndex:_currentAdIndex + 1]; } } ``` ::: Files: [`ManualCsaiViewController.swift`][manual_vc_swift] / [`.m`][manual_vc_objc], [`ManualVastPayload.swift`][manual_payload_swift] / [`.m`][manual_payload_objc], [`ManualAdBreak.swift`][manual_break_swift] / [`.m`][manual_break_objc] - guide: [manual-csai.md][manual_doc] ## Google IMA CSAI Use this example when Google IMA requests and plays your client-side ads. 1. The app requests ads with the bundled VMAP ([`ima_csai_vmap.xml`][csai_vmap]). IMA plays the ads in the app's own `AVPlayer` through `IMAAVPlayerVideoDisplay`. 2. On the IMA `STARTED` event, the app classifies the ad by `ad.adSystem` and checks its position in `ad.adPodInfo.adPosition`. 3. For an Infillion ad that can play interactively, the app pauses the `IMAAdsManager`, seeks the player to just before the end of the placeholder, and starts `TruexAdRenderer` with `ad.traffickingParameters`. Without usable `traffickingParameters`, it resumes IMA, which continues the pod. 4. On `onAdCompleted` after `onAdFreePod`, the app calls `adsManager.discardAdBreak()`. Otherwise, and on `onAdError` or `onNoAdsAvailable`, it calls `adsManager.resume()`: the placeholder finishes right away and IMA plays the next ad. Because the placeholder plays in the app's `AVPlayer`, moving past it is a plain `AVPlayer` seek. ::: code-group ```swift [Swift] private func handleAdStarted(_ ad: IMAAd) { let type = classifyImaCsaiAd(adSystem: ad.adSystem) let position = ad.adPodInfo.adPosition guard canPlayImaCsaiInteractive(type, adPosition: position) else { return } // Pause IMA and move the placeholder to its end, so it completes as soon as IMA resumes. adsManager?.pause() skipPlaceholderAd() guard let adParameters = imaCsaiAdParameters(traffickingParameters: ad.traffickingParameters) else { // No usable adParameters: don't start the renderer, continue the pod. adsManager?.resume() return } currentInteractiveType = type truexAdCreditReceived = false guard let renderer = TruexRendererFactory.renderer(withAdParameters: adParameters, delegate: self) else { // No renderer means no delegate events: continue the pod. finishInteractiveAd(skipRemainingAds: false) return } truexAdRenderer = renderer renderer.start(view) } private func finishInteractiveAd(skipRemainingAds: Bool) { disposeRenderer() if skipRemainingAds { adsManager?.discardAdBreak() } else { adsManager?.resume() } } ``` ```objective-c [Objective-C] - (void)handleAdStarted:(IMAAd *)ad { ImaCsaiAdType type = ClassifyImaCsaiAd(ad.adSystem); NSInteger position = ad.adPodInfo.adPosition; if (!CanPlayImaCsaiInteractive(type, position)) { return; } // Pause IMA and move the placeholder to its end, so it completes as soon as IMA resumes. [_adsManager pause]; [self skipPlaceholderAd]; NSDictionary *adParameters = ImaCsaiAdParameters(ad.traffickingParameters); if (!adParameters) { // No usable adParameters: don't start the renderer, continue the pod. [_adsManager resume]; return; } _currentInteractiveType = type; _truexAdCreditReceived = NO; TruexAdOptions options = DefaultOptions(); options.supportsUserCancelStream = YES; #if DEBUG options.enableWebViewDebugging = YES; #endif TruexAdRenderer *renderer = [[TruexAdRenderer alloc] initWithAdParameters:adParameters options:options delegate:self]; if (!renderer) { // No renderer means no delegate events: continue the pod. [self finishInteractiveAdSkippingRemainingAds:NO]; return; } _truexAdRenderer = renderer; [renderer start:self.view]; } - (void)finishInteractiveAdSkippingRemainingAds:(BOOL)skipRemainingAds { [self disposeRenderer]; if (skipRemainingAds) { [_adsManager discardAdBreak]; } else { [_adsManager resume]; } } ``` ::: `onAdCompleted` calls `finishInteractiveAd` with `shouldDiscardImaCsaiAdBreak(currentInteractiveType, earnedCredit: truexAdCreditReceived)`; `onAdError` and `onNoAdsAvailable` call it with `false`. Files: [`ImaCsaiViewController.swift`][csai_vc_swift] / [`.m`][csai_vc_objc], [`ImaCsaiAdPayload.swift`][csai_payload_swift] / [`.m`][csai_payload_objc] - guide: [ima-csai.md][csai_doc] ## Google IMA SSAI Use this example when Google IMA DAI returns one stream with your ads stitched in. 1. The app requests the DAI VOD stream (content source ID `2496857`, video ID `truex-content22-4k`) with `IMAAVPlayerVideoDisplay` on its `AVPlayer`. During an ad break, scrubbing is disabled. 2. On the IMA `STARTED` event, the app classifies the ad by `ad.adSystem` and its position. For an Infillion ad that can play interactively, it pauses the stream, remembers where the placeholder ends (stream time plus `ad.duration`), and starts `TruexAdRenderer`. 3. DAI has no `discardAdBreak`, so every skip is a seek. On `onAdCompleted` after `onAdFreePod`, the app seeks just past the end of the current ad break's cue point. Otherwise, and on `onAdError` or `onNoAdsAvailable`, it seeks to just before the placeholder's end, so the next stitched ad plays. 4. A time observer seeks over ad breaks that were already played. ::: code-group ```swift [Swift] /// Stream time just before the placeholder ad ends, so the stitched stream moves on to the next ad. func imaSsaiPlaceholderEndTime(adStartStreamTime: Double, adDuration: Double) -> Double { max(0, adStartStreamTime + adDuration - 0.1) } /// Stream time just past the current ad break, so the rest of the break is skipped. func imaSsaiAdBreakSkipTime(adBreakEndStreamTime: Double) -> Double { adBreakEndStreamTime + 0.1 } private func finishInteractiveAd(skipAdBreak: Bool) { disposeRenderer() let streamTime = currentStreamTime if skipAdBreak, let cuepoint = (streamManager?.cuepoints as? [IMACuepoint])?.first(where: { $0.startTime <= streamTime && streamTime < $0.endTime }) { // TrueX credit earned: seek past the whole stitched ad break seekStream(to: imaSsaiAdBreakSkipTime(adBreakEndStreamTime: cuepoint.endTime)) } else { // Opt-out, error, or IDVx: seek to the placeholder end, the next stitched ad plays seekStream(to: placeholderEndTime) } player.play() } ``` ```objective-c [Objective-C] // Stream time just before the placeholder ad ends, so the stitched stream moves on to the next ad. FOUNDATION_EXTERN double ImaSsaiPlaceholderEndTime(double adStartStreamTime, double adDuration); // Stream time just past the current ad break, so the rest of the break is skipped. FOUNDATION_EXTERN double ImaSsaiAdBreakSkipTime(double adBreakEndStreamTime); - (void)finishInteractiveAdSkippingAdBreak:(BOOL)skipAdBreak { [self disposeRenderer]; IMACuepoint *cuepoint = skipAdBreak ? [self cuepointAt:self.currentStreamTime playedOnly:NO] : nil; if (cuepoint) { // TrueX credit earned: seek past the whole stitched ad break [self seekStreamTo:ImaSsaiAdBreakSkipTime(cuepoint.endTime)]; } else { // Opt-out, error, or IDVx: seek to the placeholder end, the next stitched ad plays [self seekStreamTo:_placeholderEndTime]; } [_player play]; } ``` ::: > \[!WARNING] Demo-only `adParameters` > In a production DAI integration, read `adParameters` from the ad: `imaSsaiAdParameters(traffickingParameters: ad.traffickingParameters)` in Swift, `ImaSsaiAdParameters(ad.traffickingParameters)` in Objective-C. The sample app instead loads them from the iOS sample tags in `ImaSsaiDemoAdParameters`, because the sample DAI stream can't be changed. Delete that file when you copy the example. Files: [`ImaSsaiViewController.swift`][ssai_vc_swift] / [`.m`][ssai_vc_objc], [`ImaSsaiAdPayload.swift`][ssai_payload_swift] / [`.m`][ssai_payload_objc] - guide: [ima-ssai.md][ssai_doc] [repo]: https://github.com/socialvibe/infillion-ads-ios-examples [manual_fixture]: https://github.com/socialvibe/infillion-ads-ios-examples/blob/main/swift-ios-app/InfillionAdsExamples/Resources/manual_ad_break.json [manual_vc_swift]: https://github.com/socialvibe/infillion-ads-ios-examples/blob/main/swift-ios-app/InfillionAdsExamples/ManualCsai/ManualCsaiViewController.swift [manual_vc_objc]: https://github.com/socialvibe/infillion-ads-ios-examples/blob/main/objc-ios-app/InfillionAdsExamplesObjC/ManualCsai/ManualCsaiViewController.m [manual_payload_swift]: https://github.com/socialvibe/infillion-ads-ios-examples/blob/main/swift-ios-app/InfillionAdsExamples/ManualCsai/ManualVastPayload.swift [manual_payload_objc]: https://github.com/socialvibe/infillion-ads-ios-examples/blob/main/objc-ios-app/InfillionAdsExamplesObjC/ManualCsai/ManualVastPayload.m [manual_break_swift]: https://github.com/socialvibe/infillion-ads-ios-examples/blob/main/swift-ios-app/InfillionAdsExamples/ManualCsai/ManualAdBreak.swift [manual_break_objc]: https://github.com/socialvibe/infillion-ads-ios-examples/blob/main/objc-ios-app/InfillionAdsExamplesObjC/ManualCsai/ManualAdBreak.m [manual_doc]: https://github.com/socialvibe/infillion-ads-ios-examples/blob/main/docs/manual-csai.md [csai_vmap]: https://github.com/socialvibe/infillion-ads-ios-examples/blob/main/swift-ios-app/InfillionAdsExamples/Resources/ima_csai_vmap.xml [csai_vc_swift]: https://github.com/socialvibe/infillion-ads-ios-examples/blob/main/swift-ios-app/InfillionAdsExamples/ImaCsai/ImaCsaiViewController.swift [csai_vc_objc]: https://github.com/socialvibe/infillion-ads-ios-examples/blob/main/objc-ios-app/InfillionAdsExamplesObjC/ImaCsai/ImaCsaiViewController.m [csai_payload_swift]: https://github.com/socialvibe/infillion-ads-ios-examples/blob/main/swift-ios-app/InfillionAdsExamples/ImaCsai/ImaCsaiAdPayload.swift [csai_payload_objc]: https://github.com/socialvibe/infillion-ads-ios-examples/blob/main/objc-ios-app/InfillionAdsExamplesObjC/ImaCsai/ImaCsaiAdPayload.m [csai_doc]: https://github.com/socialvibe/infillion-ads-ios-examples/blob/main/docs/ima-csai.md [ssai_vc_swift]: https://github.com/socialvibe/infillion-ads-ios-examples/blob/main/swift-ios-app/InfillionAdsExamples/ImaSsai/ImaSsaiViewController.swift [ssai_vc_objc]: https://github.com/socialvibe/infillion-ads-ios-examples/blob/main/objc-ios-app/InfillionAdsExamplesObjC/ImaSsai/ImaSsaiViewController.m [ssai_payload_swift]: https://github.com/socialvibe/infillion-ads-ios-examples/blob/main/swift-ios-app/InfillionAdsExamples/ImaSsai/ImaSsaiAdPayload.swift [ssai_payload_objc]: https://github.com/socialvibe/infillion-ads-ios-examples/blob/main/objc-ios-app/InfillionAdsExamplesObjC/ImaSsai/ImaSsaiAdPayload.m [ssai_doc]: https://github.com/socialvibe/infillion-ads-ios-examples/blob/main/docs/ima-ssai.md --- --- url: /ads/publisher/platforms/clientjs.md --- # TrueX Ad Non-FEP Integration TrueX ads can also run outside of FEP (Full Episode Player) - integrated through Infillion's publisher-driven JavaScript ad API for websites, AKA client.js. Where FEP integrations deliver ads through your ad server into video ad pods, client.js lets your application code request a TrueX engagement ad directly and render it into your own container, with the reward defined and handled by you. The ad itself is the same opt-in TrueX product as on FEP: the reward event fires only after the user spends at least 30 seconds on the ad and interacts with it at least once, so user attention is guaranteed. The choice card is an FEP-only feature - on a website, the opt-in is your site's own UI. Typical use cases: view an ad to get free access to content or a game, earn site tokens, and similar rewarded-access experiences. It works in desktop and mobile browsers, and in WebViews inside native apps. ## How It Works 1. Create a client.js instance and check whether Infillion has an ad to show for the current user 2. Decide whether to show it, based on your own internal logic 3. Show a call-to-action (the trigger point). When the user selects it, create a placeholder container and call `client.loadAdIntoContainer(ad, container)` 4. Handle the TrueX reward event, verify it on your server, and grant the reward yourself - unlock the content, credit the tokens, etc. ## Terminology | Term | Definition | | --- | --- | | Engagement | The primary rich-media ad unit provided by TrueX. Also called an activity, or simply an ad | | `user_id` | A partner-provided alphanumeric string that uniquely identifies the current user. The Advertising ID is recommended, if supported. Previously called `network_user_id` | | `placement_hash` | A unique alphanumeric identifier generated by Infillion. Each integrating publisher placement has its own hash. Previously called `partner_config_hash` | | `application_key` | A partner-specific alphanumeric string generated by Infillion, used in server-side callback requests | | `application_secret` | A partner-specific alphanumeric string generated by Infillion, used to sign and verify engagements. Do not share this value | | Trigger point | A call-to-action that lets the user opt in and load the engagement | | Opt-in | The user selecting the trigger point to engage with the ad | ## Engagement Flow 1. Your page requests an ad from Infillion. 2. If an ad is available, your page shows a trigger point. 3. When the user opts in, the ad loads into your container (or a popup window). 4. When the user completes the ad, Infillion records the engagement and your page receives the `onCredit` event. This can happen at any time while the user is still interacting with the ad, regardless of whether they close it. 5. The user closes the ad. ## Documentation * [Integration](./integration) - include client.js, initialize it, request and show an ad, and the hybrid mode for native apps * [Events](./events) - the ad lifecycle events, and the engagement object * [Reward Verification](./reward-verification) - verify the engagement signature on your server, and the optional server-side callback * [Examples](./examples) - a live example and its source --- --- url: /ads/publisher/platforms/clientjs/integration.md --- # Integration ## Including client.js Production: ```html ``` Testing: ```html ``` An NPM package is available upon request. The script defines the global `truex` object. For legacy integrations, the same object is also available as `socialvibe`. ## Requesting and Showing an Ad Define the `options`, initialize `truex.client`, and request an ad. If an ad is available, assign the event handlers and display the trigger point. When the user selects the trigger point, call `client.loadAdIntoContainer` (preferred) or `client.openAdWindow` (popup). ```js const options = { user_id: 'XXX', placement_hash: 'YYY', }; truex.client(options, (client) => { client.requestAd((ad) => { if (!ad) { // No ad is available for this user return; } ad.onStart(() => console.log('start')); ad.onCredit((engagement) => console.log('credit', engagement.key)); ad.onFinish(() => console.log('finish')); ad.onClose(() => console.log('close')); // Signal that the trigger point is shown client.trackTriggerPointImpression(ad); displayTriggerPoint(ad); // your call-to-action }); }); ``` When the user selects the trigger point: ```js client.loadAdIntoContainer(ad, document.getElementById('ad-container'), { width: '100%', height: '100%', }); ``` Request a new ad in the `onClose` handler, so an expired ad is never shown. See the [Examples](./examples) page for a complete page. ## Options | Parameter | Required | Type | Description | | --- | --- | --- | --- | | `user_id` | Yes | string | A partner-provided alphanumeric string that uniquely identifies the current user. The Advertising ID is recommended, if supported | | `placement_hash` | Yes | string | The placement-specific identifier provided by Infillion | | `age` | | int | The user's age. Used for demographic targeting | | `gender` | | string | The user's gender: `m` for male, `f` for female, or `x` for non-binary. Used for demographic targeting | | `dimension_1` ... `dimension_5` | | string | Slots for targetable metadata, for example a TV show name | | `handle_clickthrough_manually` | | boolean | For pages that run in a native app WebView. The clickthrough URL is passed to the `onClickthrough` handler instead of being opened by the ad | ## Client Methods | Method | Description | | --- | --- | | `requestAd(callback)` | Requests an ad. The `callback` receives the `ad`, or nothing if no ad is available | | `loadAdIntoContainer(ad, container, options)` | Loads the ad iframe into `container`, a DOM element or the id of one. The optional `options` can set `width`, `height`, `minWidth`, and `minHeight`, as pixels (a number or `'960px'`) or percentages (`'100%'`). `width` and `height` default to the size of the ad | | `openAdWindow(ad)` | Opens the ad in a popup window. Use either this or `loadAdIntoContainer` | | `trackTriggerPointImpression(ad)` | Call it when the trigger point is displayed. Infillion uses it to calculate the click-through rate of your placement | | `trackOptOut(ad)` | Call it when the user decides not to engage, for example with a "no thanks" option on your trigger point. Used only for tracking | | `prepareAd(ad)` | Attaches the event handlers to an `ad` object that you received from the [Web Service API](#hybrid-mode) instead of `requestAd` | ## Deprecated Names The following names are deprecated since June 2020. They keep working, so existing integrations do not need to change. | Deprecated | Replaced by | | --- | --- | | `network_user_id` | `user_id` | | `partner_config_hash` | `placement_hash` | | `loadActivityIntoContainer` | `loadAdIntoContainer` | | `openActivityWindow` | `openAdWindow` | | `prepareActivity` | `prepareAd` | | `trackImpression` | `trackTriggerPointImpression` | ## Hybrid Mode In hybrid mode, your server or native app requests the ad through the Web Service API, and a page in a WebView or a browser shows it. Use it when the ad request can't come from the page, for example when a native app handles the request and the targeting data. ### Web Service API ``` GET https://get.truex.com/v2 ``` For testing, use `https://qa-get.truex.com/v2`. | Parameter | Required | Type | Description | | --- | --- | --- | --- | | `user.id` | Yes | string | The same as `user_id` | | `placement.key` | Yes | string | The same as `placement_hash` | | `ip` | Server calls | string | The IP address of the user, used for geo-targeting | | `user_agent` | Server calls | string | The URI-encoded user agent of the device | | `referrer` | | string | The URI-encoded URL of the page where the trigger point is shown | | `age`, `yob` | | int | The age or the 4-digit year of birth of the user | | `gender` | | string | `m`, `f`, or `x` | | `dimension_1` ... `dimension_5` | | string | Slots for targetable metadata | | `coppa` | | boolean | `1` if the user is under 13, `0` otherwise. Prevents the data from being stored | | `mraid` | | boolean | `1` adds `mraid.js` to the ad markup | Sample request: ``` https://get.truex.com/v2?placement.key=YYY&user.id=XXX ``` The response is an `ad` JSON object, or `{"error": "No bid found"}` if no ad is available: | Field | Description | | --- | --- | | `id` | The id of the ad creative | | `campaign_id` | The id of the ad campaign | | `name` | The name of the ad creative | | `window_url` | The URL that renders the ad | | `window_width`, `window_height` | The size of the ad, in pixels. Size the container to this | | `currency_amount` | The amount of your currency that the user earns for completing the ad | | `revenue_amount` | The revenue you earn for the ad | | `session_id` | The id of the current session | ```json { "id": 7633, "campaign_id": 3802, "name": "Kung Fu Panda", "window_url": "https://qa-media.truex.com/...", "window_width": 960, "window_height": 500, "currency_amount": 2, "revenue_amount": "0.1", "session_id": "i_wktnzfS1G6yqdp57lSIw" } ``` ### Showing the Ad in a WebView Pass the response to a page that includes client.js. `prepareAd` attaches the event handlers and the tracking, so you receive the same events as with `requestAd`: ```js function showAd(placementHash, userId, ad) { const options = { user_id: userId, placement_hash: placementHash, handle_clickthrough_manually: true, }; truex.client(options, (client) => { client.prepareAd(ad); // Pass the events to the native app, for example through a WebView message handler ad.onStart(() => sendToNative('onStart')); ad.onCredit((engagement) => sendToNative('onCredit', engagement)); ad.onClickthrough((url) => sendToNative('onClickthrough', url)); ad.onFinish(() => sendToNative('onFinish')); ad.onClose(() => sendToNative('onClose')); client.loadAdIntoContainer(ad, document.getElementById('ad-container'), { width: '100%', height: '100%', }); }); } ``` `sendToNative` is yours: for example `webkit.messageHandlers..postMessage` on iOS or a `@JavascriptInterface` object on Android. The native app calls `showAd` after it loads the page. > \[!NOTE] > The client-side events come from client.js. If you load `window_url` in a WebView without client.js, the only completion signal is the [server-side callback](./reward-verification#server-side-callback). --- --- url: /ads/publisher/platforms/clientjs/events.md --- # Events ## Ad Events The `ad` object that you receive from `requestAd` has the following event handlers. Assign them before you show the ad. | Handler | Description | | --- | --- | | `onStart` | The ad loaded and the user can start interacting | | `onCredit` | The user spent at least 30 seconds on the ad and interacted at least once. This is the billing event: the advertiser is billed and you earn your per-unit payment. It yields an [engagement](#engagement-object) | | `onFinish` | The user reached the end of the ad | | `onClose` | The ad window closed. It fires independently of `onFinish` and `onCredit`, and can fire before `onStart` if the user closes the window while the ad is still loading. Request a new ad here, so an expired ad is never shown | | `onMessage` | The ad passes a custom [message](#messages) to your page | | `onError` | An error occurred with the ad. Treat it as an exception, and remove the ad container | | `onClickthrough` | A clickthrough happened. It fires only when `handle_clickthrough_manually` is `true`. The handler receives the `url`, and your app decides how to open it | ```js ad.onCredit((engagement) => { // Send engagement to your server, verify it, then grant the reward }); ``` ## Engagement Object The `engagement` object is passed to the `onCredit` handler. It tells your page that the user fulfilled the ad requirements and can be rewarded, and it carries the data that your server needs to [verify](./reward-verification) the reward. | Attribute | Description | | --- | --- | | `ad` | The ad that the user engaged with, including `creative_id` and `currency_amount` | | `signature` | The authentication digest of the engagement | | `signature_argument_string` | The attributes of the engagement as a query string. The signature is a digest of this value | | `key` | A unique id of the engagement. It contains alphanumeric characters and `.`, `-`, `_` | | `completed_at` | The ISO 8601 date and time when the engagement was completed | | `message_id` | A unique transaction id, for debugging | ## Messages The `onMessage` handler receives the following messages: | Message | Description | | --- | --- | | `true_targeting_started` | TrueX showed the user a TrueTargeting question | | `true_targeting_complete` | The user completed the TrueTargeting question | | `ad_passback` | You can ask Infillion to limit the number of consecutive TrueTargeting questions. When the limit is reached, `ad_passback` fires so you can waterfall to a different ad source | ```js ad.onMessage((payload) => { if (payload === 'ad_passback') { // Remove the TrueX ad and waterfall to the next ad source } else if (payload === 'true_targeting_started') { // Track that the question started } else if (payload === 'true_targeting_complete') { // Track that the question completed } }); ``` --- --- url: /ads/publisher/platforms/clientjs/reward-verification.md --- # Reward Verification The `onCredit` event runs in the user's browser, so a user can fake it. Before you grant a reward, verify the engagement on your server. This page shows how to verify the engagement signature, and how to receive and verify the optional server-side callback. ## Engagement Signature The `engagement` object that your page receives in `onCredit` has a `signature`. It is an HMAC-SHA256 digest of the `signature_argument_string`, calculated with your `application_secret`. Your page sends the engagement to your server, and your server: 1. Calculates the digest with the `application_secret` and compares it with the `signature` 2. Checks that the `user_id` and the `placement_hash` in the signed string are the ones you expect 3. Checks that the engagement key was not used before, so the same engagement can't reward the user twice > \[!WARNING] Keep the secret on the server > Calculating the digest in the browser means shipping your `application_secret` to the user. Always verify on your server. The `signature_argument_string` is a query string with the following keys, sorted alphabetically: `completed_at`, `creative_id`, `currency_amount`, `engagement_key`, `network_user_id`, and `partner_config_hash`. It contains the legacy names of `user_id` and `placement_hash`. ```js import { createHmac, timingSafeEqual } from 'node:crypto'; const usedEngagementKeys = new Set(); // use your database in production function verifyEngagement(engagement, expected) { const { signature, signature_argument_string: signed } = engagement; // 1. The signature matches const digest = createHmac('sha256', process.env.APPLICATION_SECRET) .update(signed) .digest('hex'); const a = Buffer.from(digest); const b = Buffer.from(String(signature)); if (a.length !== b.length || !timingSafeEqual(a, b)) { return false; } // 2. The engagement is for this user and this placement const params = new URLSearchParams(signed); if ( params.get('network_user_id') !== expected.userId || params.get('partner_config_hash') !== expected.placementHash ) { return false; } // 3. The engagement was not used before const key = params.get('engagement_key'); if (usedEngagementKeys.has(key)) { return false; } usedEngagementKeys.add(key); return true; } ``` ## Server-Side Callback Most partners use the client-side `onCredit` event with the verification above, and it is enough for most use cases. If a client-side callback is not possible, you can use a server-side callback instead. Give Infillion a callback URL during the integration. When a user completes an engagement, Infillion sends a request to the URL, usually in real time, but it can be delayed under load. The request has the following parameters: | Parameter | Type | Description | | --- | --- | --- | | `application_key` | string | The partner-specific application key provided by Infillion | | `network_user_id` | string | The `user_id` of the user who completed the ad | | `currency_amount` | int | The amount of currency earned | | `currency_label` | string | The label of the currency, for example `coins` | | `revenue` | decimal | The revenue you earned for the engagement, with up to 8 decimal places | | `placement_hash` | string | The hash of the placement of the engagement | | `campaign_name` | string | The name of the campaign | | `campaign_id` | string | The id of the campaign | | `creative_name` | string | The name of the creative | | `creative_id` | string | The id of the creative | | `engagement_id` | string | The unique id of the engagement. If you receive an `engagement_id` twice, ignore the request and return the failure code `3`, so the user is not credited twice | | `sig` | string | The signature of the request | Your callback URL must respond with one of the following codes: | Code | Meaning | | --- | --- | | `0` | Recoverable failure. The request is retried | | `1` | The callback was processed | | `2` | Invalid signature. Infillion is notified to investigate | | `3` | Invalid user or duplicate `engagement_id`. The request is not retried | ### Callback Signing All callbacks are signed to protect both Infillion and publishers from forged requests. To verify the `sig`: 1. Put all the parameters except `sig` in `key=value` pairs 2. Sort the pairs alphabetically by key 3. Concatenate the pairs, with no separator 4. Append the `application_secret` 5. Calculate the HMAC-SHA1 digest of the string, with the `application_secret` as the key, and encode it in Base64 ```js import express from 'express'; import { createHmac, timingSafeEqual } from 'node:crypto'; const app = express(); const seenEngagementIds = new Set(); // use your database in production function callbackSignature(params, applicationSecret) { const base = Object.keys(params) .filter((key) => key !== 'sig') .sort() .map((key) => `${key}=${params[key]}`) .join('') + applicationSecret; return createHmac('sha1', applicationSecret).update(base).digest('base64'); } app.get('/truex/callback', (req, res) => { const expected = Buffer.from( callbackSignature(req.query, process.env.APPLICATION_SECRET) ); const actual = Buffer.from(String(req.query.sig)); if (expected.length !== actual.length || !timingSafeEqual(expected, actual)) { res.send('2'); // invalid signature return; } const { engagement_id: engagementId, network_user_id: userId } = req.query; if (seenEngagementIds.has(engagementId) || !isKnownUser(userId)) { res.send('3'); // duplicate engagement or unknown user return; } seenEngagementIds.add(engagementId); grantReward(userId, Number(req.query.currency_amount)); res.send('1'); // processed }); ``` `isKnownUser` and `grantReward` are yours. ### Security Besides the signature and the `engagement_id` check, you can block all callback requests except those from the following IP addresses: ``` 50.16.245.107 50.16.245.109 50.16.245.111 184.73.195.45 204.236.225.149 184.73.184.220 174.129.192.205 174.129.193.108 ``` --- --- url: /ads/publisher/platforms/clientjs/examples.md --- # Examples A simple page that requests a TrueX ad with client.js, shows a trigger point, and loads the ad into a container when the user selects it. The whole integration is one page, so there is no separate reference app. > \[!WARNING] Live ads > The example requests a real ad. Staying with the ad and interacting with it behaves like a real TrueX ad. ## Live Example Select the trigger, interact with the ad for about 30 seconds, and watch the log. When the user closes the ad, the page requests a new one. ## What the Example Shows * `truex.client` is created with the `user_id` and the `placement_hash`. The demo generates a new `user_id` for each page load. * `client.requestAd` returns an ad, or nothing if no ad is available. The page shows the trigger point only when there is an ad, and calls `trackTriggerPointImpression`. * The ad opens in the page's container with `loadAdIntoContainer`, sized with `width` and `height` options. * `onCredit` fires after about 30 seconds and one interaction. This is where your page sends the engagement to your server, which [verifies](./reward-verification) it and grants the reward. It is shown in bold in the log. * `onClose` removes the container and requests a new ad, so an expired ad is never shown. ## Full Listing The page that runs in the live example above: ```html client.js demo
``` --- --- url: /ads/publisher/platforms.md --- # Choose Your Integration Infillion Ads can be integrated on every major streaming device, as well as directly on websites. Find your integration option below. ## FEP FEP (Full Episode Player) refers to apps that stream long-form video content with ad breaks. FEP integrations deliver TrueX and IDVx ads through your ad server into your video player's ad pods. ### How the SDKs relate All FEP integrations are built around the TrueX Ad Renderer (TAR). Some SDKs are native implementations, others are wrappers around the Web/HTML renderer: | SDK | Type | | --- | --- | | [Web/HTML](./web/) | The base TAR renderer for browsers and HTML5 apps | | [Android](./android/) | Native wrapper around the Web/HTML renderer (TV & mobile) | | [iOS](./ios/) | Native wrapper around the Web/HTML renderer | | [Vega](./vega/) | React Native wrapper around the Web/HTML renderer | | [Roku](./roku/) | Native TAR component library (plus an Innovid/RAF option) | | [tvOS](./tvos/) | Native TAR library | ### Roku * **[Roku SDK](./roku/)** - integrate the TAR component library directly into your BrightScript channel * **Innovid SDK embedded into RAF** - documentation coming soon ### Fire TV * **[Vega SDK](./vega/)** - for apps built on Amazon's Vega (Kepler) OS * **[Android SDK](./android/)** - for Android-based Fire TV apps * **[Web/HTML SDK](./web/)** - for webview-based apps ### Android TV * **[Android SDK](./android/)** - for native apps on Android TV and Google TV * **[Web/HTML SDK](./web/)** - for webview-based apps ### tvOS * **[tvOS SDK](./tvos/)** - native library for Apple TV apps ### Mobile * **[Android SDK](./android/)** - for native Android phone and tablet apps * **[iOS SDK](./ios/)** - for native iPhone and iPad apps ### Desktop & Web * **[Web/HTML SDK](./web/)** - browser-based integration for desktop, plus HTML5-based CTV apps and game consoles * **[VPAID](./web/vpaid)** - for web players that load ads through a client-side ad manager, such as Google IMA, Brightcove, or Bitmovin ## Non-FEP Non-FEP integrations are publisher-driven: your code requests an engagement ad directly and renders it outside the video ad pods. ### Websites * **[client.js](./clientjs/)** - publisher-driven JS ad API for running TrueX ads on your site: check ad availability, render the ad into your own container, and grant the reward yourself - unlock content, site tokens, and more --- --- url: /ads/publisher/overview/getting-started-interactive-video.md --- ### How Interactive Video (IDVx) Works **Interactive Video (IDVx)** delivers a new industry standard in video engagement across all devices and platforms. The product goes beyond hovers and inadvertent clicks, to deliver real interaction, by real humans, in premium environments. Built on the same core interactive foundation as TrueX, Interactive Video removes the opt-in, interaction, and time spent requirements, in order to capitalize on the scale of a Publisher's inventory. Infillion pays Publishers a set CPM for the IDVx ads the Publisher renders in their ad pods. **Key features:** * **Automatic start** - Begins playing without opt-in; there is no choice card * **Interactive throughout** - Users can interact with ad content during playback, but no interaction is required * **Capped at 30 seconds** - Unlike the unlimited time a user can spend in a TrueX ad * **Plays inline** - Completes and continues to the next ad in the pod; it does not skip or suppress the Publisher's ad pod ![Interactive Video ad pod flow](/assets/images/image5.jpg) ***Fig. A** example of an IDVx ad playing inline within a regular ad pod* #### User Flow 1. A viewer is streaming video content and reaches a commercial break 2. The content is paused to load the ad pod 3. The IDVx ad is loaded like any other ad, and is one of many in the Publisher's ad pod 4. When the user comes across the IDVx ad, they can interact with it if they choose 5. IDVx ads are 30 seconds long, so when those 30 seconds are up the ad ends 6. The video player then loads either another ad or the video content #### Supported Platforms IDVx ads are available on the following platforms: * [Vega (FireTV)](/platforms/vega/) * [Web/HTML](/platforms/web/) - desktop browsers and HTML5-based CTV apps * [Roku](/platforms/roku/) * [Android](/platforms/android/) - TV & mobile * [iOS](/platforms/ios/) * [tvOS](/platforms/tvos/) - coming soon ## Flighting the IDVx Placement Tag The IDVx placement tag is a 1 stage tag and can be flighted in every ad slot of a pod (except any reserved for TrueX) according to the priority the Publisher wants to give Infillion in filling that slot. For example the Publisher may wish to put the IDVx tag behind direct sold campaign tags, but ahead of programmatic ad tags. Or the Publisher may wish to give the IDVx tag the first position in every last slot of every pod. :::steps 1. The Publisher's AdOps team schedules the IDVx placement tag in any priority they wish throughout their network in their ad server 2. If the Publisher already has the TrueX SDK installed, they need to make a few changes to the integration code ([see below](#changes-existing-truex-publishers-need-to-make-to-support-idvx-ads)) 3. When the ad pod is triggered by the user, the IDVx ad tag will request an ad from the Infillion ad server (we support both CSAI and SSAI) 4. If a match is found for the user, the Infillion ad server will send an IDVx ad response that will trigger the IDVx user flow detailed above 5. If no IDVx ad is matched for the user by the Infillion ad server, an empty VAST response will be passed back, triggering whatever is scheduled next - either another video ad or the content ::: ## Changes existing TrueX Publishers need to make to support IDVx ads 1. Previously, TrueX Publishers had to add logic to show the container when the ad slot is 0 and ad system type is "trueX". Publishers now must make the logic more permissive and allow the container to also show ads with system type "IDVx" in any slot. 2. While you can continue to initialize "trueX" ads using a VAST config URL, "IDVx" ads must be initialized with VAST parameters JSON objects. 3. Lastly, SSAI integrations have been using `lastAdEndTime` to track pod progress. We must continue to do that with IDVx, but even more vigorously than before. For full details on the above-mentioned changes, here are the diffs from upgrading our reference apps to support IDVx: [CSAI](https://github.com/socialvibe/truex-ctv-google-ima-csai-ref-app/compare/887b609...eb9bd72), [SSAI](https://github.com/socialvibe/truex-ctv-google-ima-ssai-ref-app/compare/a749a14...655912c) ## Creative Example This animated gif illustrates the interactivity of an IDVx ad creative. ![Interactive Video creative example](/assets/images/image3.gif) > \[!TIP] > The [Infillion Engagement Showcase](https://engagements.infillion.com/showcase/) shows working examples of TrueX and IDVx ads on all supported devices and platforms. If the ad experience does not load right away in the demo environment, refresh the page and it should load properly. --- --- url: /ads/publisher/overview/getting-started-truex.md --- ### Product Flow **TrueX** presents users with an **interactive choice card** where they choose between engaging with branded content or watching standard video ads. When users opt in and complete the interactive experience, they earn an **ad credit that skips all remaining ads** in the pod, creating a win-win: viewers get ad-free content, and advertisers get highly engaged audiences. **Key features:** * **Opt-in via choice card** - Users actively choose to engage * **Engagement requirements** - Users must spend 30 seconds and interact at least once to earn credit * **Skips entire ad break** - Successful engagement bypasses all remaining ads in the pod Upon completion, the renderer signals a `adFreePod` event, indicating that remaining linear ads should be skipped. There are two distinct product flows supported by `TruexAdRenderer`: Sponsored Stream (full-stream ad-replacement) and Sponsored Ad Break (mid-roll ad-replacement). In a Sponsored Ad Break flow, once the user hits a mid-roll break with a **TrueX** tag flighted, they will be shown a "choice-card" offering them the choice between watching a normal set of video ads or a fully interactive **TrueX** ad: ![choice card](/assets/images/choice_card.png) ***Fig. A** example **TrueX** mid-roll choice card* If the user opts for a normal ad break, or if the user does not make a selection before the countdown timer expires, the **TrueX** UI will close and playback of normal video ads can continue as usual. If the user opts to interact with **TrueX**, an interactive ad unit will be shown to the user: ![ad](/assets/images/ad.png) ***Fig. B** example **TrueX** interactive ad unit* The requirement for the user to "complete" this ad is for them to spend at least the allotted time on the unit and for at least one interaction (e.g. navigating anywhere through the ad). ![true attention timer example](/assets/images/true-attention-timer-example.png) ***Fig. C** example **TrueX** attention timer* Once the user fulfills both requirements, a "Watch Your Show" button will appear in the bottom right, which the user can select to exit the **TrueX** ad. Having completed a **TrueX** ad, the user will be returned directly to content, skipping the remaining ads in the current ad pod. The Sponsored Stream flow is quite similar. In this scenario, a user will be shown a choice-card in the pre-roll: ![choice card](/assets/images/choice_card.png) ***Fig. D** example **TrueX** pre-roll choice card (full-stream replacement)* Similarly, if the user opts-in and completes the **TrueX** ad, they will be skipped over the remainder of the pre-roll ad break. However, every subsequent mid-roll break in the current stream will also be skipped over. In this case instead of the regular pod of video ads, the user will be shown a "hero card" (also known as a "skip card"): ![skip card](/assets/images/skip_card.png) ***Fig. E** example **TrueX** mid-roll skip card* This messaging will be displayed to the user for several seconds, after which they will be returned directly to content. ## High-level Integration Process The Infillion Integration is composed of three high level steps - flighting our tag to your Ad server, enabling the rendering of Infillion interactive Ads in your video player, and crediting users for viewing a Infillion Ad (Ad skipping logic). :::steps 1. Tag flighting VAST tags are flighted at the top of every midroll Ad pod. This enables both the fetching of Infillion interactive Ads, and skipping of Ad pods in the event a viewer has already completed a Infillion Ad. Our tag does not represent a single campaign or advertiser, but instead is an always-on tag that changes based on demand for a particular user. Tag type differs based on the selected integration type. VPAID (for desktop) & VAST companion tags are the most common Flighting Instructions 2. Start TruexAdRenderer. When the player reaches an Infillion ad, start the TrueX Ad Renderer (TAR) for your platform with the `adParameters` from the VAST response - see your platform's integration guide. On Roku with RAF, as a part of our partnership with Innovid - Infillion choice card & interactive ad rendering support are provided through the Innovid SDK and don’t require any special development effort from the publisher. 3. Credit users with free Ad pods when they complete a Infillion Ad. * When users complete a Infillion Ad, we need to deliver their reward (skipped Ad breaks). This is typically handled by the publisher. * When a user completes a Infillion Ad, our SDK fires the `adFreePod` event followed by the `adCompleted` event, at which point the publisher is responsible for skipping the remaining ads in the pod and resuming playback of the main content stream. A more in depth guide on Ad skipping logic can be found in the technical documentation. ::: ## Flighting Instructions: Once your team is ready to start testing Infillion Ads, our team will provide a test tag to flight to your Ad server. This test tag allows us to deliver test traffic to your Ad placement either in your staging environment, or a public placement with very low test demand allocation. This allows a limited number of users, including our QA team, to test your integration before it goes into production with real campaigns. Exact flighting instructions vary based on the specific Ad server & version your company uses. Below are general guidelines for flighting Infillion tags on a wide variety of Ad servers. Notes on tag flighting: > \[!IMPORTANT] > The Infillion TrueX tag should only be flighted to the first position in a given Ad pod. It will not function otherwise. > \[!NOTE] > In order to ensure that TrueX choice cards don’t reduce ad slots in the case the viewer decides not to interact with a Infillion Ad, increase the max number of Ads in pods which include a Infillion Ad by 1. For instance, if your normal commercial break pattern for a pod has a maximum of 3, 30 second Ads (90 seconds total), increase to a maximum of 4, 30 second Ads (120 seconds total). If a user spends 30 seconds in the Infillion choice card, a 120 second pod limit means they’ll still see the intended 3x 30 second Ads rather than being cut off. [truex_product]: https://infillion.com/products/infillion-truex/ [interactive_video_product]: https://infillion.com/products/infillion-interactive-video/