AppleTV SDK integration guide

This integration guide explains how to integrate Xandr's AppleTV SDK into your AppleTV application, so you can use Xandr's client-side ad insertion to bid on, track, and measure client-side video advertising within your app. The instructions and examples assume the following:

  • You're developing against Apple tvOS 12.0 or higher.
  • You have existing instances of AVPlayer and AVPlayerView Controller in your development environment
  • You're using the Swift code libraries at Swift Core Libraries: Foundation. The minimum version is SWIFT 4.2.
  • You're using Xcode v10.0 or higher.

This application supports the /ptv and /vmap endpoints.

The following sections explain the steps needed to complete the integration, provide a reference for customization options, suggest best practices, and make recommendations for developers who prefer to use Objective-C.

Setting up the integration

1. Set up and import CocoaPods

CocoaPods is used to distribute Xandr's SDK and integrate it into your app. Use the following links to install CocoaPods and add it to your project.

You can use the following code in your pod spec file:

Sample podfile:

target 'MyApp' do
pod 'AppNexusTVOSSDK', '~> 2.0.0'
end

2. Import Xandr's AppleTV SDK module into UIViewController

Use the following commands:

import ANTVSDK
import AVKit //standard Audio Video Kit library

3. Add the AdControllerProtocol protocol to UIViewController

Use the following commands:

class VMAPViewController: UIViewController, AdControllerProtocol {

4. Make your instances of AVPlayerViewController and AVPlayer available

Use the following commands:

let appContentVideoPlayer:AVPlayer? = AVPlayer(url: URL(string: "<URL to your content video>")!)
let appContentViewController:AVPlayerViewController? = AVPlayerViewController()

5. Create an instance of AdController

The adController object should be scoped within UIControllerVIew. Use the following command:

let adController = AdController()

6. (Optional) Define the AdSlot object

We provide the AdSlot object as a parameter for communication between application and SDK. AdSlot represents an ad break (or "ad pod") that contains multiple VAST objects. As you can see in the example, the VastData array accommodates multiple ads in a single break.

Initial "setup" method will return Array<AdSlot> representing VMAP ad breaks.

@objc public class AdSlot: NSObject, Codable {
    public let breakId: String?;
    public var timeOffset: String?;
    public let breakType: String?;
    public let vastDatas: Array<VastData>;
    public let currentAdId: String?;
    public let timeToShowAdBreak: Int?;
}

7. Implement the required adPlaybackControllerDidSetup delegate method

Use this delegate method in UIViewController to account for preroll ads before content playback.

func adPlaybackControllerDidSetup(adSlots: Array<AdSlot>?) {
    if (adSlots == nil) {
        /// fatal SDK error, start playback of your app content video here
    } else {AdControllerProtocol
        /// SDK called ad URL and got a response from the server and all internal properties are being set and ready to go
        adController.play();//send play() signal to SDK
    }
}

8. Implement the required adPlaybackControllerShouldStartAd delegate method

This delegate method must be implemented in UIViewController to notify SDK you are ready for video ad playback. When the SDK determines that it is time to show a video ad, it will pause the content video and invoke this method with a single "AdSlot" object. The publisher application can then implement any ad-specific behavior. Note that this function should return true. If it returns false, the ad will not be shown.

func adPlaybackControllerShouldStartAd(adSlot: AdSlot?) -> Bool {
    return true // if publisher application allows SDK to play video ad for the "adSlot"
}

9. Implement the required adPlaybackControllerDidNotifyAdSlotEnded delegate method

This delegate method must be implemented in UIViewController so the SDK can notify the program that the video ad slot has finished playing all the ad creatives. When this delegate is called, the SDK will resume playback of the content video.

func adPlaybackControllerDidNotifyAdSlotEnded(adSlot: AdSlot?) {
    //do publisher application's internal work here
}

10. Implement additional delegate methods

Implementing these delegate methods is required. However, they can remain stub functions.

/// a delegate when sdk raised an error
///
/// - Parameters:
///   - adSlot: AdSlot where error occurred
///   - result: ANTVErrorProrotocol
/// - Returns: void
func adPlaybackControllerDidRaiseAnError(adSlot: AdSlot?, result: ANTVErrorProtocol?) {
    //do stuff on publisher application when SDK reports an error
}
  
/// A delegate SDK triggered a event
///
/// - Parameters:
///   - adSlot: AdSlot where event triggered
///   - result: any object
/// - Returns: void
func adPlaybackControllerDidNotifyAdSlotEnded(adSlot: AdSlot?) {
    //do stuff on publisher application when SDK notifies an event
}

Implementation methods

Call setup()

Invoke the setup() method to initialize the SDK and request an ad. When the SDK is ready, it will invoke the adPlaybackControllerDidSetup delegate method. Note that passing contentVideoPlayerViewController, contentVideoPlayer, and contentUIViewController allows the SDK to stop content video playback when it is time to show an ad break, as well as to show ad break marks on the content video timeline.

Note

To initialize the Apple TV SDK, contentVideoPlayer needs to have video assets at the time when setup() is called.

The following examples show how to invoke setup for VMAP, for a Xandr placement set, for a VAST URL, and for Xandr VAST placement.

// VMAP URL
adController.setup(vmapURL:String, contentVideoPlayerViewController: AVPlayerViewController, contentVideoPlayer: AVPlayer, contentUIViewController: UIViewController, delegate: AdControllerProtocol)
// Xandr Placement Set Id
adController.setup(appNexusPsetId: Int, contentVideoPlayerViewController: AVPlayerViewController?, contentVideoPlayer: AVPlayer?, contentUIViewController: UIViewController?, delegate: AdControllerProtocol)
// VAST URL
adController.setup(vastUrl: String, contentVideoPlayerViewController: AVPlayerViewController?, contentVideoPlayer: AVPlayer?, contentUIViewController: UIViewController?,delegate: AdControllerProtocol)
// Xandr VAST Placement ID
adController.setup(appNexusMemberId: Int, appNexusPlacementId: Int, contentVideoPlayerViewController: AVPlayerViewController?, contentVideoPlayer: AVPlayer?, contentUIViewController: UIViewController?,delegate: AdControllerProtocol

Pause/resume ad

Invoke pauseAd()/resumeAd() to control the video ad. getPauseStatus() will return the current pause state of the ad.

adController.pauseAd() //pause video ad
adController.resumeAd() //resume video ad
adController.getPauseStatus() //true if video ad paused, false otherwise

Skip ad

To render a skip button, implement the adPlaybackControllerDidNotifyAnEvent() delegate method. The SDK logic adds a target to handle UIControlEvents.primaryActionTriggered which will call an API skip(). This VideoEvent.timeToShowSkip trusts the VAST skipoffset attribute.

func adPlaybackControllerDidNotifyAnEvent(adSlot: AdSlot?, event: VideoEvent?, data: String?) {
    if (event == VideoEvent.timeToShowSkip) {
         
        //render skip button
        button.frame = CGRect(x: 0, y: 0, width: 450, height: 100)
        button.setTitle("Click to Skip", for: .normal)
        button.addTarget(self, action: #selector(self.skipClicked), for: .primaryActionTriggered)
        self.view.addSubview(button)
         
        //update focus engine to let user click a button
        self.setNeedsFocusUpdate()
        self.updateFocusIfNeeded()
    }  
}
 
@objc func skipClicked() {
    self.adController.skip() //SDK skip call
     
    //remove target and button
    self.button.removeTarget(nil, action: nil, for: .allEvents)
    self.button.removeFromSuperview()
}

Customizing SDK behavior

You can use the setOptions() method to specify how you want the SDK to look, what text you want to display with ads, whether the video ad displays a skip button, and other features. Note that setOptions() must be invoked before setup().

All these settings are optional. The following example shows how to set them:

adController.setOptions(
    widthOfAdIndicator: 100,
    leftMarginOfAdIndicator: 200,
    bottomMarginOfAdIndicator: 300,
    backgroundColor: UIColor.black,
    heightOfAdIndicator: 400,
    fontColor: UIColor.blue,
    alphaOfAdIndicator: 0.5,
    widthOfAdCountdown: 500,
    heightOfAdCountdown: 600,
    adText: "advertisement",
    isSkippable: true);
    ...

setOptions() parameter reference

Parameter Data Type Description
adText String?=nil The text of the ad indicator (for example Ad)
alphaOfAdIndicator Double?=nil The alpha channel (transparency) value of the ad indicator.
- 1.0=non-transparent
- 0.5=50% transparent
- 0=completely transparent (and therefore invisible)
backgroundColor UIColor?=nil The background color of the ad indicator.
bottomMarginOfAdIndicator Int?=nil The height of the bottom margin of the ad indicator in pixels.
disableSelectGestureOverride Bool?=nil Whether the publisher app, not Xandr, should control the remote trackpad's gesture action. To let Xandr override the normal gesture and enable a skip action, set this parameter to false. For example, true.
fontColor UIColor?=nil The font color of the ad indicator.
heightOfAdCountdown Int?=nil The height of the ad countdown in pixels.
heightOfAdIndicator Int?=nil The height of the ad indicator in pixels.
isSkippable Bool?=nil Whether a skip button is displayed, allowing the viewer to skip the ad. For example, true.
leftMarginOfAdIndicator Int?=nil The width of the left margin of the ad indicator in pixels.
widthOfAdCountdown Int?=nil The width of the ad countdown in pixels. The ad countdown is the area that displays the number of seconds the ad plays for. If the ad is skippable, it displays the number of seconds until the viewer can click the skip button.
widthOfAdIndicator Int?=nil The width of the ad indicator in pixels. The ad indicator is the section of the ad indicating it is advertising, and not requested content.

Sample code for UIViewController

This example uses Swift.

import Foundation
import ANTVSDK
import AVKit
import os
 
class DevViewController: UIViewController,  AdControllerProtocol {
     
    static let contentURL: String = "<your content video url>"
     
    let adController = AdController()
    let appContentVideoPlayer:AVPlayer? = AVPlayer(url: URL(string: contentURL)!)
    let appContentViewController:AVPlayerViewController? = AVPlayerViewController()
    let button = UIButton.init(type: .system)
     
    override func viewDidLoad() {
        super.viewDidLoad()
        self.initANTVSDK()
    }
     
    deinit {
        adController.dismiss() //dismiss all objects created on ANTVSDK
    }
    func initANTVSDK() -> Void {
 
        //setup options
        adController.setOptions(isSkippable: true, disableSelectGestureOverride: true)       
        adController.setup(vmapURL: "<your vmap url>", contentVideoPlayerViewController: appContentViewController!, contentVideoPlayer: appContentVideoPlayer!, contentUIViewController: self, delegate: self)
         
    }
     
    func adPlaybackControllerDidSetup(adSlots: Array<AdSlot>?) {
        adController.play()
    }
     
    func adPlaybackControllerShouldStartAd(adSlot: AdSlot?) -> Bool {
        return true;
    }
 
    @objc func skipClicked() {
        self.adController.skip()
         
        //remove target and button
        self.button.removeTarget(nil, action: nil, for: .allEvents)
        self.button.removeFromSuperview()
    }
     
    func adPlaybackControllerDidNotifyAdSlotEnded(adSlot: AdSlot?) {
        Logger.debug("got adPlaybackControllerDidNotifyAdSlotEnded")
        exit(0)
    }
     
    func adPlaybackControllerDidRaiseAnError(adSlot: AdSlot?, result: ANTVErrorProtocol?) {
        Logger.error("got error from SDK: %@", (result?.description)!)
    }
     
    func adPlaybackControllerDidNotifyAnEvent(adSlot: AdSlot?, event: VideoEvent?, data: String?) {
        if (event == VideoEvent.timeToShowSkip) {
             
            //render skip button
            button.frame = CGRect(x: 0, y: 0, width: 450, height: 100)
            button.setTitle("Click to Skip", for: .normal)
            button.addTarget(self, action: #selector(self.skipClicked), for: .primaryActionTriggered)
            self.view.addSubview(button)
             
            //update focus engine to let user click a button
            self.setNeedsFocusUpdate()
            self.updateFocusIfNeeded()
        }  
    }  
}

Tips and best practices

Use the following best practices when implementing the SDK in your app.

Updating the DFP correlator

When you call a DFP tag with ANTVSDK, the correlator ensures the publisher receives a proper response from the DFP server. Make sure you change the correlator parameter on your tag every time you make this call, as shown in the following example:

https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/ad_rule_samples&ciu_szs=300x250&ad_rule=1&impl=s&gdfp_req=1&env=vp&output=vmap&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ar%3Dpreonly&cmsid=496&vid=short_onecue&correlator={unique random number}

Letting the SDK initiate the content video

Your application should not start the content video on its own. It should wait until the SDK has loaded ads, and possibly shown a pre-roll ad. Implementing adPlaybackControllerDidSetup ensures the SDK starts your content video. When you use this method, preroll ads will be properly displayed before the content video in your app begins.

Memory management

tvOS ARC (Automatic Reference Counting) checks and cleans most of the used objects in ANTVSDK that are not necessary at the time when the publisher application's UIViewController is dismissed. However, to support and ensure the validity of ARC, calling adController.dismiss() on the deinit block of the publisher applications's UIViewController (as shown in the following example) is required.

deinit {
    adController.dismiss() //dismiss all objects created on ANTVSDK
}

Testing in non-secure mode

By default, Xcode does not support non-secure HTTP requests. Certain development-time testing and debugging situations might require using non-secure HTTP tags. To temporarily enable full access to HTTP protocol, check the "Project Info" tab to make sure the project allows arbitrary loads.

Managing x86_64 and ARM64 binary versions

A framework "ANTVSDK.framework" includes x86_64 and ARM64 binary to support both an emulator and an actual Apple TV device. In case you don't want to include the x86_64 binary in your application because of Apple AppStore guidelines, you can simply remove it using lipo, as shown in the following example:

cd ANTVSDK.framework
lipo -remove x86_64 ANTVSDK -o ANTVSDK

You can then check to see if the library only contains the ARM64 binary:

<username>:ANTVSDK.framework $ file ANTVSDK
ANTVSDK: Mach-O universal binary with 1 architecture: [arm64: Mach-O 64-bit dynamically linked shared library arm64]
ANTVSDK (for architecture arm64):   Mach-O 64-bit dynamically linked shared library arm64
<username>:ANTVSDK.framework $

Objective C project requirements

By default, XCode doesn't have a setting to use Swift Standard Libraries for Objective-C projects. For an Objective-C-based publisher application, you'll need to set the "Always Embed Swift Standard Libraries” setting to Yes in the Build Settings for the IDE.