|
|
Line 3: |
Line 3: |
|
| |
|
| == Overview == | | == Overview == |
| | The Nielsen SDK is one of multiple framework SDKs that Nielsen provides to enable measuring linear (live) and on-demand TV viewing using TVs, mobile devices, etc. |
| The App SDK is the framework for mobile application developers to integrate Nielsen Measurement into their media player applications. It supports a variety of Nielsen Measurement Products like Digital in TV Ratings, Digital Content Ratings ([[DCR & DTVR]]), and [[Digital Ad Ratings]] (DAR). Nielsen SDKs are also equipped to measure static content and can track key life cycle events of an application like: | | The App SDK is the framework for mobile application developers to integrate Nielsen Measurement into their media player applications. It supports a variety of Nielsen Measurement Products like Digital in TV Ratings, Digital Content Ratings ([[DCR & DTVR]]), and [[Digital Ad Ratings]] (DAR). Nielsen SDKs are also equipped to measure static content and can track key life cycle events of an application like: |
| *Application launch events and how long app was running | | *Application launch events and how long app was running |
| *Time of viewing a sub section / page in the application. | | *Time of viewing a sub section / page in the application. |
|
| |
|
| __TOC__
| | If the content being played contains ID3 tags, when played on a mobile device or within a browser, these tags can be sent to Nielsen for collection/processing via the Nielsen SDK. |
| | | <blockquote> |
| == Prerequisites ==
| | VOD in TV Ratings (formally knows as Recently Telecast VOD) support is now available; however, you must notify us to ensure accurate reporting. |
| To start using the App SDK, the following details are required:
| | </blockquote> |
| * '''App ID (appid):''' Unique ID assigned to the player/site and configured by product.
| | [[DCR_Video_iOS_SDK|This guide covers implementation steps for iOS]] using Xcode utilizing the Standard Nielsen SDK for DCR. |
| * '''sfcode:''' Unique identifier for the environment that the SDK should point to.
| |
| * '''Nielsen SDK:''' The Nielsen SDK package contains a variety of sample players for your reference.
| |
| If you do not have any of these prerequisites or if you have any questions, please contact our SDK sales support team. | |
| Refer to [[Digital Measurement Onboarding]] guide for information on how to get a Nielsen App SDK and appid.
| |
| | |
| == Simplified API ==
| |
| As part of making the SDK more user-friendly and reducing the number of app integration touch points, Nielsen has designed a simple interface to pass metadata to the SDK. The new trackevent() API has been implemented as a wrapper for the existing SDK and will be responsible for handling new API calls, performing validation, and translating new API calls to the existing Nielsen App SDK API methods. Applications which are already integrated with the existing SDK API are unaffected by this new API.
| |
| [[File:SimplifiedAPI_vs_StandardAPI_New.jpg|2048px|link=https://engineeringportal.nielsen.com/w/images/9/91/SimplifiedAPI_vs_StandardAPI_New.jpg]]
| |
| In the Simplified API, one API call will be used to reference many methods. The key-value structure of this API call will provide information for proper crediting. The SDK will utilize the data received in this call and translate it into a sequence of calls linking to the existing API.
| |
| | |
| [[File:Co-Existance.jpg|center|700px]]
| |
| ''For iOS SDK framework package will contain 2 public header files. One header file will contain old SDK interface and will be used by existing clients (NielsenAppApi.h). New API will be defined in a new public header file (NielsenEventTracker.h).''
| |
| | |
| ''For Android, a new wrapper class for AppSdk will be introduced (AppSdkTrackEvent). This class will be responsible for handling and translating new API calls into calls of the existing Nielsen App SDK API methods. A new public API will be introduced in this class, that accepts a JSONObject parameter.''
| |
| | |
| == Implementation ==
| |
| This guide covers implementation steps for iOS using Xcode.
| |
| | |
| == Setting up your Development Environment ==
| |
| Prior to SDK Version 6.2.0.0 the IOS framework has been distributed as a static library packaged into framework bundle format. Apple recommends to use dynamic framework, it has some benefits over static libraries like less executable file size of an app, faster startup time and native support in xCode IDE. Nielsen AppSDK has been transformed into dynamic framework in this release ([[iOS_Static_Framework_Setup|static framework]] is still available).
| |
| | |
| If migrating from the static library to this new dynamic framework, once implemented, unless your specific application requires, you can remove the following Frameworks that were once required:<code> [AdSupport, JavascriptCore, SystemConfiguration, Security, AVFoundation, libc++] </code>
| |
| <br>
| |
| | |
| The Dynamic framework is created as a fat framework. It means that it contains slices required for devices (armv7, arm64) as well as slices required for simulators (i386, x86_64). Simulator slices are needed to let clients build and debug their app on the simulators, but they should be removed before sending the app to the AppStore. The example of the shell script that should be added as a Run Script phase in the application can be [[DCR_Video_iOS_SDK#Removing_Simulators|found below]].
| |
| | |
| === How to obtain the NielsenAppApi.Framework ===
| |
| The Nielsen AppSDK can either be downloaded, or integrated directly within an application through the use of a CocoaPod.
| |
| * [[Special:Downloads|Select to Download Directly]]
| |
| * [[Digital_Measurement_iOS_Artifactory_Guide|Select to obtain Cocoapod implementation guide]]
| |
| | |
| === Configuring Xcode Development Environment ===
| |
| Starting with SDK version 6.0.0.0, the Nielsen App SDK is compatible with Apple iOS versions 8.0 and above. In addition, the framework supports modules, so all the required frameworks are linked automatically as the are needed. More details can be found here: https://stackoverflow.com/questions/24902787/dont-we-need-to-link-framework-to-xcode-project-anymore
| |
| | |
| <blockquote>'''Note''': All communications between the SDK and the Census (Collection Facility) use HTTPS.</blockquote>
| |
| | |
| === Download Framework ===
| |
| The first step is to download and copy the [[Special:Downloads|NielsenAppApi.framework]] bundle to the app project directory.
| |
| === Add Framework ===
| |
| In the General tab for app configuration add NielsenAppApi.framework in the list of Embedded Binaries.
| |
| === Add Path ===
| |
| Add path to the NielsenAppApi.framework in the Framework Search Paths build setting.
| |
| === Import Framework ===
| |
| Add NielsenAppApi.framework module in the source file of your app:
| |
| | |
| ==== Using Swift ====
| |
| You can use Objective-C and Swift files together in a single project, no matter which language the project used originally. This makes creating mixed-language app and framework targets as straightforward as creating an app or framework target written in a single language.
| |
| | |
| For more detailed information regarding importing [https://developer.apple.com/documentation/swift/imported_c_and_objective-c_apis/importing_objective-c_into_swift Objective-C into Swift]
| |
| | |
| ==== Using Objective-C ====
| |
| Add the code to the View Controller’s header file.
| |
| <syntaxhighlight lang ="objective-c">
| |
| #import <NielsenAppApi/NielsenEventTracker.h>
| |
| </syntaxhighlight>
| |
| | |
| == SDK Initialization ==
| |
| The latest version of the Nielsen App SDK allows instantiating multiple instances of the SDK object, which can be used simultaneously without any issue. The sharedInstance API that creates a singleton object was deprecated prior to version 5.1.1.
| |
| | |
| * Starting at version 6.0.0 of the SDK, there is no limit on the number of instances.
| |
| | |
| The following table contains the list of arguments that should be passed during initialization.
| |
| | |
| * The appid is provided by the Nielsen Technical Account Manager (TAM).
| |
| {| class="wikitable"
| |
| |-
| |
| ! Parameter / Argument !! Description !! Source !! Required? !! Example
| |
| |-
| |
| | appid || Unique id for the application assigned by Nielsen.
| |
| It is GUID data type, provided by the Nielsen<br> Technical Account Manager (TAM).
| |
| || Nielsen-specified || Yes || <code>PXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX</code>
| |
| |-
| |
| | appname || Name of the application || Client-defined || No|| "Nielsen Sample App"
| |
| |-
| |
| | appversion || Current version of the app used || Client-defined || No || "1.0.2"
| |
| |-
| |
| | sfcode || Specifies the Nielsen collection<br> facility to which the SDK should connect
| |
| '''DTVR'''
| |
| * "us"
| |
| '''Digital Audio'''
| |
| * "drm"
| |
| '''DCR'''
| |
| * "dcr"
| |
| || Nielsen-specified || Yes || "dcr"
| |
| |-
| |
| |nol_devDebug || Enables Nielsen console logging and is only required for testing ||Nielsen-specified || Optional || <code>"DEBUG"
| |
| "INFO"<br>
| |
| "WARN"
| |
| |}
| |
| <br />
| |
| === Debug flag for development environment ===
| |
| Player application developers / integrators can use Debug flag to check whether an App SDK API call made is successful. To activate the Debug flag,
| |
| Pass the argument <code> "nol_devDebug": "INFO" </code>, in the JSON string . The permitted values are:
| |
| | |
| * '''INFO''': Displays the API calls and the input data from the application (validate player name, app ID, etc.). It can be used as certification Aid.
| |
| * '''WARN''': Indicates potential integration / configuration errors or SDK issues.
| |
| * '''ERROR''': Indicates important integration errors or non-recoverable SDK issues.
| |
| * '''DEBUG''': Debug logs, used by the developers to debug more complex issues.
| |
| | |
| Once the flag is active, it logs each API call made and the data passed. The log created by this flag is minimal.
| |
| <blockquote>'''Note''': DO NOT activate the Debug flag in a production environment.</blockquote> | |
| <br>
| |
| | |
| ==== Sample SDK Initialization Code ====
| |
| {{ExampleCode|
| |
| |Swift =
| |
| Swift Example:
| |
| | |
| <syntaxhighlight lang="swift">
| |
| NielsenInit.swift
| |
| | |
| import Foundation
| |
| import NielsenAppApi
| |
| | |
| class NielsenInit : NSObject {
| |
| class func createEventTracker(delegate: NielsenEventTrackerDelegate) -> NielsenEventTracker?{
| |
|
| |
| //Initialising the NielsenEventTracker class by passing app information which returns the instance of NielsenEventTracker.
| |
|
| |
| var nielsenEventTracker: NielsenEventTracker?
| |
|
| |
| let appInformation = [
| |
|
| |
| "appid": "PDA7D5EE6-B1B8-4123-9277-2A788XXXXXXX",
| |
| "appversion": "1.0",
| |
| "appname": "Amazing app",
| |
| "sfcode": "dcr",
| |
| "nol_devDebug": "DEBUG"
| |
| ]
| |
|
| |
| nielsenEventTracker = NielsenEventTracker(appInfo:appInformation1, delegate:delegate)
| |
| return nielsenEventTracker
| |
| }
| |
| }
| |
| </syntaxhighlight>
| |
| | |
| | |
| Sample code: ViewController
| |
| <br />
| |
| <code>ViewController.swift</code>
| |
| | |
| <syntaxhighlight lang="swift">
| |
| | |
| override func viewDidLoad() {
| |
| super.viewDidLoad()
| |
|
| |
| //Getting the instance of NielsenEventTracker
| |
|
| |
| self.nielsenEventTracker = NielsenInit.createEventTracker(delegate: self)
| |
| </syntaxhighlight>
| |
| | |
| |Objective C =
| |
| Initialize the Nielsen App object within the viewDidLoad view controller delegate method using initWithAppInfo:delegate:
| |
| <blockquote>If App SDK is initialized using init or new methods, it will ignore the API calls resulting in no measurement. The SDK will not return any errors.</blockquote>
| |
| Objective-C Example:
| |
| <code>NielsenInit.m</code>
| |
| <syntaxhighlight lang="objective-c">
| |
| #import "NielsenInit.h"
| |
| #import <NielsenAppApi/NielsenEventTracker.h>
| |
| | |
| @implementation NielsenInit
| |
| | |
| + (NielsenEventTracker *)createNielsenEventTrackerWithDelegate:(id<NielsenEventTrackerDelegate>)delegate
| |
| {
| |
| //Initialising the NielsenEventTracker class by passing app information which returns the instance of NielsenEventTracker.
| |
|
| |
| NSDictionary *appInformation = @{ @"appid": @"PDA7D5EE6-B1B8-4123-9277-2A788XXXXXXX",
| |
| @"appversion": @"1.0",
| |
| @"appname": @"Amazing app",
| |
| @"sfcode": @"dcr",
| |
| @"nol_devDebug": @"DEBUG"};
| |
|
| |
| return [[NielsenEventTracker alloc] initWithAppInfo:appInformation delegate:delegate];
| |
| }
| |
| </syntaxhighlight>
| |
| | |
| The ViewController.m file could then contain the following line(s):
| |
| <syntaxhighlight lang="objective-c">
| |
| //Getting the instance of NielsenEventTracker
| |
| nielsenEventTracker = [NielsenInit createNielsenEventTrackerWithDelegate:nil];
| |
| /////
| |
| | |
| }
| |
| </syntaxhighlight>
| |
| | |
| }}
| |
| | |
| <!--
| |
| == Initializing the Nielsen AppSDK to measure the Viewability ==
| |
| The integrator to support the viewability metrics in the application has to provide a tag value of the player view to let Nielsen AppSDK know that there is a player that needs to be tracked. It’s called the ‘containerId’ and it should be passed in application info dictionary as string while initializing the Nielsen AppSDK.
| |
| | |
| ==== Android ====
| |
| {| class="wikitable"
| |
| |-
| |
| ! # !! Parameter Name !! Description !! Supported Values !! Example
| |
| |-
| |
| | 1 || containerId ||View ID of the UI element used as player view in application. getId() method of View class can be used to get this value. ||A positive integer used to identify the view. || 2131558561
| |
| |}
| |
| | |
| | |
| ==== iOS ====
| |
| {| class="wikitable"
| |
| |-
| |
| ! # !! Parameter Name !! Description !! Supported Values !! Example
| |
| |-
| |
| | 1 || containerId ||The tag of the UIView that represents the Player View ||The string value representing the NSInteger value with maximum value of NSIntegerMax that is related on 32- or 64-bit applications. || "100" <br> "2131558561"
| |
| |}
| |
| | |
| For iOS it is required to link additional frameworks that are needed for viewability engine:<br>
| |
| <code>JavaScriptCore.framework</code> <br>
| |
| <code>WebKit.framework</code>
| |
| | |
| The Nielsen AppSDK uses a tracking WebView (TWV) approach. For more information on Viewability, please refer to [https://engineeringportal.nielsen.com/docs/Implementing_Viewability_with_AppSDK Implementing Viewability with AppSDK.]
| |
| -->
| |
| | |
| == APP SDK Error & Event Codes ==
| |
| To view the Error and Event codes for iOS and Android, please review the [[APP SDK Event Codes|App SDK Event Code]] Reference page.
| |
| | |
| == Simplified API Syntax ==
| |
| The existing API has a number of methods used for reporting player and application state changes to the SDK. The order of calls is important for the SDK in the existing API. In the new simplified API, all these calls will be replaced with one API call that will get one dictionary object with many key-value pairs, where any value could be another complex dictionary object. All the data provided in the older API in separate calls will be provided in one single call.
| |
|
| |
| Main API call for the new NielsenEventTracker API:
| |
| | |
| <syntaxhighlight lang="objective-c">
| |
| - (void)trackEvent:(NSDictionary *)data;
| |
| </syntaxhighlight>
| |
| | |
| === Handling JSON Metadata ===
| |
| Parameter “data” is a JSON object with many key-value pairs that holds all information required by SDK.
| |
| | |
| Format of input object is the following:
| |
| <syntaxhighlight lang="json">
| |
| {
| |
| "event": <event identifier>,
| |
| "type": <type of metadata>,
| |
| "metadata":{
| |
| "content": <content metadata object>,
| |
| "ad": <ad metadata object>,
| |
| "static": <static metadata object>
| |
| },
| |
| "playheadPosition":<playhead value | Unix Timestamp>,
| |
| "id3Data": <id3 payload>,
| |
| "ottData": <ott info object>,
| |
| "optout": <optout string>
| |
| }
| |
| </syntaxhighlight>
| |
| <br>
| |
| | |
| === Event Types ===
| |
| The New API method supports the following event types:
| |
| {| class="wikitable"
| |
| !Key
| |
| !Description
| |
| |-
| |
| |'''playhead'''||
| |
| It is used to pass content, ad or static metadata, the current playhead value, Unix timestamp or id3 payload, OTT information to the SDK.
| |
| |-
| |
| |'''pause'''||
| |
| This event should be used to in the following cases:<br>
| |
| * application enters background,<br>
| |
| * any application interruptions,<br>
| |
| * content playback is paused.<br>
| |
| (Pause is detected by SDK automatically only if the time gap between commands exceeds 30 minutes.)
| |
| |-
| |
| |'''complete'''||
| |
| This event should be called when the session is completed, ends, or the user initiates a stop.
| |
| |-
| |
| |'''adStop'''||
| |
| This event should be called at the end of each ad and is required when advertisements have the same assetId.
| |
| |}
| |
| <br>
| |
| DCR and DTVR require various levels of data. Please select the tab for the product you are interested in reviewing.
| |
| {{DCRDTVRTabs
| |
| |DCR=
| |
| === Digital Content Ratings===
| |
| <table>
| |
| <tr>
| |
| <th> Parameter
| |
| </th>
| |
| <th> <b>Description</b>
| |
| </th>
| |
| <th> <b>Supported values</b>
| |
| </th>
| |
| <th> <b>Example</b>
| |
| </th></tr>
| |
| <tr>
| |
| <td> <b>event</b>
| |
| </td>
| |
| <td> Event identifier
| |
| </td>
| |
| <td>
| |
| <p><code> String:
| |
| playhead, pause,
| |
| complete, adStop</code>
| |
| </p>
| |
| </td>
| |
| <td><syntaxhighlight lang="swift">"event":"playhead"</syntaxhighlight>
| |
| </td></tr>
| |
| <tr>
| |
| <td> <b>type</b>
| |
| </td>
| |
| <td> Determines the metadata object
| |
| that should be used for crediting.
| |
| </td>
| |
| <td>
| |
| <p><code> String:<br />
| |
| content,
| |
| ad,
| |
| static</code>
| |
| </p>
| |
| </td>
| |
| <td><syntaxhighlight lang="swift">"type":"content"</syntaxhighlight>
| |
| </td></tr>
| |
| <tr>
| |
| <td> <b>metadata</b>
| |
| </td>
| |
| <td> Object that holds metadata values of specific types<br />
| |
| <p><span style="color:blue"> Detailed in tables below</span>
| |
| </p>
| |
| </td>
| |
| <td> <code>Object</code>
| |
| </td>
| |
| <td><syntaxhighlight lang="swift">
| |
| "metadata":{
| |
| "content": <content metadata object>,
| |
| "ad": <ad metadata object>,
| |
| "static": <static metadata object>
| |
| },
| |
| </syntaxhighlight>
| |
| </td></tr>
| |
| <tr>
| |
| <td> <b>playheadPosition</b>
| |
| </td>
| |
| <td> Playhead value as reported by video player. Unix timestamp (seconds since Jan-01-1970 UTC) for live video.
| |
| </td>
| |
| <td> <code>String</code>
| |
| </td>
| |
| <td>
| |
| <p>Position value is Unix timestamp (live):
| |
| <code>
| |
| "playheadPosition":"1542797780"</code>
| |
| </p><p>Position value is playhead:
| |
| <code>
| |
| "playheadPosition":"10"</code>
| |
| </p>
| |
| </td></tr>
| |
| </table>
| |
| === Content Metadata ===
| |
| Content metadata sent for every playheadPosition update.
| |
| <table>
| |
| <tr>
| |
| <th> Key </th>
| |
| <th> Description </th>
| |
| <th> Example </th>
| |
| <th> Required
| |
| </th></tr>
| |
| <tr>
| |
| <td>'''assetName''' </td>
| |
| <td> name of program (100 character limit) </td>
| |
| <td> <code>"MyTest789"</code> </td>
| |
| <td> Yes
| |
| </td></tr>
| |
| <tr>
| |
| <td> '''assetid''' </td>
| |
| <td> unique ID assigned to asset </td>
| |
| <td> "B66473" </td>
| |
| <td> Yes
| |
| </td></tr>
| |
| <tr>
| |
| <td> '''length''' </td>
| |
| <td> length of content in seconds </td>
| |
| <td> <code>"3600"</code> (0 for live stream or unknown) </td>
| |
| <td> Yes
| |
| </td></tr>
| |
| <tr>
| |
| <td> '''program''' </td>
| |
| <td>name of program (100 character limit) </td>
| |
| <td> <code>"MyProgram"</code> </td>
| |
| <td> Yes
| |
| </td></tr>
| |
| <tr>
| |
| <td> '''segB''' </td>
| |
| <td> custom segment B ¹</td>
| |
| <td> <code>"CustomSegmentValueB"</code> </td>
| |
| <td> No
| |
| </td></tr>
| |
| <tr>
| |
| <td> '''segC''' </td>
| |
| <td> custom segment C ¹</td>
| |
| <td> <code>"segmentC"</code> </td>
| |
| <td> No
| |
| </td></tr>
| |
| <tr>
| |
| <td> '''title''' </td>
| |
| <td>name of program (100 character limit) </td>
| |
| <td> <code>"S2,E3"</code> </td>
| |
| <td> Yes
| |
| </td></tr>
| |
| <tr>
| |
| <td>'''type'''</td>
| |
| <td><code>'content', 'ad', 'static'</code></td>
| |
| <td> <code> 'content'</code> </td>
| |
| <td> Yes
| |
| </td></tr>
| |
| <tr>
| |
| <td>'''section''' </td>
| |
| <td> Unique Value assigned to page/site section </td>
| |
| <td> <code>"HomePage"</code> </td>
| |
| <td> Yes
| |
| </td></tr>
| |
| <tr>
| |
| <td> '''airdate''' </td>
| |
| <td> the airdate in the linear TV ² </td>
| |
| <td> <code>"20180120 10:00:00"</code> </td>
| |
| <td> Yes
| |
| </td></tr>
| |
| <tr>
| |
| <td> '''isfullepisode''' </td>
| |
| <td> full episode flag </td>
| |
| <td> <code>"y"</code>- full episode, <code>"n"</code>- not a full episode </td>
| |
| <td> Yes
| |
| </td></tr>
| |
| <tr>
| |
| <td> '''crossId1''' </td>
| |
| <td> standard episode ID </td>
| |
| <td> "Standard Episode ID" </td>
| |
| <td> Yes
| |
| </td></tr>
| |
| <tr>
| |
| <td> '''crossId2'''</td>
| |
| <td> content originator (only required for distributors) </td>
| |
| <td> Provided by Nielsen </td>
| |
| <td> Yes (if distributor)
| |
| </td></tr>
| |
| <tr>
| |
| <td> <b>adloadtype</b>
| |
| </td>
| |
| <td> linear ("1") vs dynamic ("2") ad model
| |
| </td>
| |
| <td> "1"
| |
| </td>
| |
| <td>Yes
| |
| </td></tr>
| |
| </table>
| |
| ¹ '''Custom segments''' (segB and segC) can be used to aggregate video and/or static content within a single brand to receive more granular reports.<br>
| |
| ² Acceptable '''Air Date''' Formats:
| |
| <syntaxhighlight lang="json">
| |
| YYYYMMDD HH24:MI:SS
| |
| YYYYMMDDHH24MISS
| |
| YYYY-MM-DDTHH:MI:SS
| |
| YYYY-MM-DDHH:MI:SS
| |
| YYYYMMDDHH:MI:SS
| |
| MM-DD-YYYY
| |
| YYYYMMDD HH:MI:SS
| |
| </syntaxhighlight>
| |
| <br>
| |
| For USA all times should be EST, for all other countries Local Time.
| |
| Below is a sample event for DCR. If there are no ad or static values, the values for these keys can be left as blank/null.
| |
| <syntaxhighlight lang="json">
| |
| {
| |
| "event": "playhead",
| |
| "type": "content",
| |
| "metadata": {
| |
| "content":{
| |
| "assetName":"Big Buck Bunny",
| |
| "assetid":"B66473",
| |
| "length":"3600",
| |
| "program":"MyProgram",
| |
| "segB":"CustomSegmentValueB",
| |
| "segC":"segmentC",
| |
| "title":"S2,E3",
| |
| "type":"content",
| |
| "section":"cloudApi_app",
| |
| "airdate":"20180120 10:00:00",
| |
| "isfullepisode":"y",
| |
| "crossId1":"Standard Episode ID",
| |
| "crossId2" :"Content Originator",
| |
| "adloadtype":"2"},
| |
| "ad": {},
| |
| "static": {}
| |
| },
| |
| "playheadPosition": "",
| |
| }
| |
| </syntaxhighlight>
| |
| | |
| |DTVR=
| |
| === Digital TV Ratings info ===
| |
| <table>
| |
| <tr>
| |
| <th> Parameter
| |
| </th>
| |
| <th> <b>Description</b>
| |
| </th>
| |
| <th> <b>Supported values</b>
| |
| </th>
| |
| <th> <b>Example</b>
| |
| </th></tr>
| |
| <tr>
| |
| <td> <b>event</b>
| |
| </td>
| |
| <td> Event identifier
| |
| </td>
| |
| <td>
| |
| <p><code> String:
| |
| playhead,pause,complete,
| |
| adStop</code>
| |
| </p>
| |
| </td>
| |
| <td><syntaxhighlight lang="swift">"event":"playhead"</syntaxhighlight>
| |
| </td></tr>
| |
| <tr>
| |
| <td> <b>type</b>
| |
| </td>
| |
| <td> Determines the metadata object that should be used for crediting.
| |
| </td>
| |
| <td>
| |
| <p><code> String:<br />
| |
| content,
| |
| ad,
| |
| static</code>
| |
| </p>
| |
| </td>
| |
| <td><syntaxhighlight lang="swift">"type":"content"</syntaxhighlight>
| |
| </td></tr>
| |
| <tr>
| |
| <td> <b>metadata</b>
| |
| </td>
| |
| <td> Object that holds metadata values of specific types. <br />
| |
| <p><span style="color:blue"> Detailed in tables below</span>
| |
| </p>
| |
| </td>
| |
| <td> <code>Object</code>
| |
| </td>
| |
| <td><syntaxhighlight lang="swift">
| |
| "metadata":{
| |
| "content": <content metadata object>,
| |
| "ad": <ad metadata object>,
| |
| "static": <static metadata object>
| |
| },
| |
| </syntaxhighlight>
| |
| </td></tr>
| |
| <tr>
| |
| <td> <b>playheadPosition</b>
| |
| </td>
| |
| <td> Playhead value or Unix timestamp
| |
| </td>
| |
| <td> <code>String</code>
| |
| </td>
| |
| <td>
| |
| <p>Position value is Unix timestamp:
| |
| <code>
| |
| "playheadPosition":"1501225191747"</code>
| |
| </p><p>Position value is playhead:
| |
| <code>
| |
| "playheadPosition":"10"</code>
| |
| </p>
| |
| </td></tr>
| |
| <tr>
| |
| <td> <b>id3Data</b>
| |
| </td>
| |
| <td> Nielsen ID3 payload
| |
| </td>
| |
| <td> <code>String</code>
| |
| </td>
| |
| <td>
| |
| <p><code>
| |
| "id3Data": "www.nielsen.com/065H2g6E7ZyQ5UdmMAbbpg
| |
| ==/_EMc37zfVgq_8KB7baUYfg==/ADQCAmgV1Xyvnynyg60
| |
| kZO_Ejkcn2KLSrTzyJpZZ-QeRn8GpMGTWI7-HrEKzghxyzC
| |
| yBEoIDv2kA2g1QJmeYOl5GnwfrLDVK2bNLTbQxr1z9VBfxa
| |
| hBcQP5tqbjhyMzdVqrMKuvvJO1jhtSXa9AroChb11ZUnG1W
| |
| VJx2O4M=/33648/22847/00"</code>
| |
| </p>
| |
| <tr>
| |
| | |
| </td></tr>
| |
| </table>
| |
| | |
| === Content Metadata ===
| |
| Content metadata sent for every playheadPosition update.
| |
| <table>
| |
| <tr>
| |
| <th> Key </th>
| |
| <th> Description </th>
| |
| <th> Example </th>
| |
| <th> Required
| |
| </th></tr>
| |
| <tr>
| |
| <td>'''channelName''' </td>
| |
| <td> name of program (32 character limit) </td>
| |
| <td> <code>"MyTest789"</code> </td>
| |
| <td> Yes
| |
| </td></tr>
| |
| <tr>
| |
| <td> '''type''' </td>
| |
| <td> 'content', 'ad', 'static' </td>
| |
| <td> "content" </td>
| |
| <td> Yes
| |
| </td></tr>
| |
| <tr>
| |
| <td> <b>adModel</b>
| |
| </td>
| |
| <td> linear ("1") vs dynamic ("2") ad model
| |
| </td>
| |
| <td> "1"
| |
| </td>
| |
| <td>Yes
| |
| </td></tr>
| |
| </table>
| |
| | |
| Below is a sample event for DTVR. If there are no ad or static values, the values for these keys can be left as blank/null.
| |
| <syntaxhighlight lang="json">
| |
| {
| |
| "event": "playhead",
| |
| "type": "content",
| |
| "metadata": {
| |
| "content":{
| |
| "adModel":"1",
| |
| "channelname":"channel1"
| |
| },
| |
| "ad": {},
| |
| "static": {}
| |
| },
| |
| "playheadPosition": "",
| |
| "id3Data": "www.nielsen.com/065H2g6E7ZyQ5UdmMAbbpg==/_
| |
| EMc37zfVgq_8KB7baUYfg==/ADQCAmgV1Xyvnynyg60kZO_Ejkcn
| |
| 2KLSrTzyJpZZ-QeRn8GpMGTWI7-HrEKzghxyzCyBEoIDv2kA2g1Q
| |
| JmeYOl5GnwfrLDVK2bNLTbQxr1z9VBfxahBcQP5tqbjhyMzdVqrMK
| |
| uvvJO1jhtSXa9AroChb11ZUnG1WVJx2O4M=/33648/22847/00"
| |
| }
| |
| </syntaxhighlight>
| |
| |DCRDTVR=
| |
| === Applies to DCR and DTVR ===
| |
| <table>
| |
| <tr>
| |
| <th> Parameter
| |
| </th>
| |
| <th> <b>Description</b>
| |
| </th>
| |
| <th> <b>Supported values</b>
| |
| </th>
| |
| <th> <b>Example</b>
| |
| </th></tr>
| |
| <tr>
| |
| <td> <b>event</b>
| |
| </td>
| |
| <td> Event identifier
| |
| </td>
| |
| <td>
| |
| <p><code> String:
| |
| playhead,
| |
| pause,
| |
| complete,
| |
| adStop</code>
| |
| </p>
| |
| </td>
| |
| <td><syntaxhighlight lang="swift">"event":"playhead"</syntaxhighlight>
| |
| </td></tr>
| |
| <tr>
| |
| <td> <b>type</b>
| |
| </td>
| |
| <td> Determines the metadata object that should be used for crediting.
| |
| </td>
| |
| <td>
| |
| <p><code> String:<br />
| |
| content,
| |
| ad,
| |
| static</code>
| |
| </p>
| |
| </td>
| |
| <td><syntaxhighlight lang="swift">"type":"content"</syntaxhighlight>
| |
| </td></tr>
| |
| <tr>
| |
| <td> <b>metadata</b>
| |
| </td>
| |
| <td> Object that holds metadata values of specific types<br />
| |
| <p><span style="color:blue"> Detailed in tables below</span>
| |
| </p>
| |
| </td>
| |
| <td> <code>Object</code>
| |
| </td>
| |
| <td><syntaxhighlight lang="swift">
| |
| "metadata":{
| |
| "content": <content metadata object>,
| |
| "ad": <ad metadata object>,
| |
| "static": <static metadata object>
| |
| },
| |
| </syntaxhighlight>
| |
| </td></tr>
| |
| <tr>
| |
| <td> <b>playheadPosition</b>
| |
| </td>
| |
| <td> Playhead value or Unix timestamp (seconds since Jan-01-1970 UTC)
| |
| </td>
| |
| <td> <code>String</code>
| |
| </td>
| |
| <td>
| |
| <p>Position value is Unix timestamp:
| |
| <code>
| |
| "playheadPosition":"1501225191747"</code>
| |
| </p><p>Position value is playhead:
| |
| <code>
| |
| "playheadPosition":"10"</code>
| |
| </p>
| |
| </td></tr>
| |
| <tr>
| |
| <td> <b>id3Data</b>
| |
| </td>
| |
| <td> Nielsen ID3 payload
| |
| </td>
| |
| <td> <code>Object</code>
| |
| </td>
| |
| <td>
| |
| <p><code>
| |
| "id3Data": "www.nielsen.com/065H2g6E7ZyQ5UdmMAbbpg
| |
| ==/_EMc37zfVgq_8KB7baUYfg==/ADQCAmgV1Xyvnynyg60
| |
| kZO_Ejkcn2KLSrTzyJpZZ-QeRn8GpMGTWI7-HrEKzghxyzC
| |
| yBEoIDv2kA2g1QJmeYOl5GnwfrLDVK2bNLTbQxr1z9VBfxa
| |
| hBcQP5tqbjhyMzdVqrMKuvvJO1jhtSXa9AroChb11ZUnG1W
| |
| VJx2O4M=/33648/22847/00"</code>
| |
| </p>
| |
| </td></tr>
| |
| <tr>
| |
| <td> <b>ottData</b>
| |
| </td>
| |
| <td> Object that holds OTT information
| |
| </td>
| |
| <td> <code>Object</code>
| |
| </td>
| |
| <td><syntaxhighlight lang="swift">
| |
| "ottData": {
| |
| "ottStatus": 1,
| |
| "ottType": casting,
| |
| "ottDevice": chromecast,
| |
| "ottDeviceID": 1234
| |
| }
| |
| </syntaxhighlight>
| |
| </td></tr>
| |
| </table>
| |
| === Content Metadata ===
| |
| Content metadata sent for every playheadposition update.
| |
| <table>
| |
| <tr>
| |
| <th> Keys </th>
| |
| <th> Description </th>
| |
| <th> Example </th>
| |
| <th> Required
| |
| </th></tr>
| |
| <tr>
| |
| <td> '''length''' </td>
| |
| <td> length of content in seconds </td>
| |
| <td> <code>seconds</code> (0 for live stream) </td>
| |
| <td> Yes
| |
| </td></tr>
| |
| <tr>
| |
| <td>'''type'''</td>
| |
| <td><code>"content", "ad", "static"</code></td>
| |
| <td> <code> "content"</code> </td>
| |
| <td> Yes
| |
| </td></tr>
| |
| <tr>
| |
| <td> <b>adModel</b>
| |
| </td>
| |
| <td> linear vs dynamic ad model
| |
| </td>
| |
| <td> 1=Linear
| |
| 2=Dynamic Ads
| |
| </td>
| |
| <td>custom
| |
| </td></tr>
| |
| <tr>
| |
| <td> <b>adloadtype</b>
| |
| </td>
| |
| <td> DCR Ad Model
| |
| </td>
| |
| <td> 1=Linear
| |
| 2=Dynamic Ads
| |
| </td>
| |
| <td>custom
| |
| </td></tr>
| |
| </table>
| |
| + '''Custom segments''' (segB and segC) can be used to aggregate video and/or static content within a single Brand to receive more granular reports within a brand.<br>
| |
| ++ Acceptable '''Air Date''' Formats:
| |
| <syntaxhighlight lang="json">
| |
| YYYYMMDD HH24:MI:SS
| |
| YYYYMMDDHH24MISS
| |
| YYYY-MM-DDTHH:MI:SS
| |
| YYYY-MM-DDHH:MI:SS
| |
| YYYYMMDDHH:MI:SS
| |
| MM-DD-YYYY
| |
| YYYYMMDD HH:MI:SS
| |
| </syntaxhighlight>
| |
| <br>
| |
| Below is a sample event for DCR/DTVR joint integration. If no ad or static values, these can be left as blank/null.
| |
| <syntaxhighlight lang="json">
| |
| {
| |
| "event": "playhead",
| |
| "type": "content",
| |
| "metadata": {
| |
| "content":{
| |
| "type":"content",
| |
| "length":"0",
| |
| "adModel":"1",
| |
| "adloadtype":"1"},
| |
| "ad": {},
| |
| "static": {}
| |
| },
| |
| "playheadPosition": "",
| |
| "id3Data": "www.nielsen.com/065H2g6E7ZyQ5UdmMAbbpg==/_
| |
| EMc37zfVgq_8KB7baUYfg==/ADQCAmgV1Xyvnynyg60kZO_Ejkcn
| |
| 2KLSrTzyJpZZ-QeRn8GpMGTWI7-HrEKzghxyzCyBEoIDv2kA2g1Q
| |
| JmeYOl5GnwfrLDVK2bNLTbQxr1z9VBfxahBcQP5tqbjhyMzdVqrMK
| |
| uvvJO1jhtSXa9AroChb11ZUnG1WVJx2O4M=/33648/22847/00"
| |
| }
| |
| </syntaxhighlight>
| |
| }}
| |
| | |
| === Ad Metadata ===
| |
| The ad metadata (if applicable) should be passed for each individual ad, if ads are available during or before the stream begins.
| |
| <br/>
| |
| {| class="wikitable"
| |
| |-
| |
| ! Key !! Description !! Example !! Required
| |
| |-
| |
| | assetid || unique ID assigned to ad (no [[Special Characters]]) || <code>"AD12345"</code> || Yes
| |
| |-
| |
| | title || unique name assigned to ad || <code>"ADtitle"</code> || No
| |
| |-
| |
| |adldx || ad index (See the "Managing Ads" section below) || <code> "66478364" </code> || Yes
| |
| |-
| |
| | type || type of ad ("preroll", "midroll", or "postroll") || <code> "preroll" </code> || Yes
| |
| |-
| |
| |length || length of ad, in seconds || <code> "20" </code> || No
| |
| |}
| |
| | |
| === Ad Metadata Sample ===
| |
| <syntaxhighlight lang="json">
| |
| {
| |
| "ad": {
| |
| "assetid":"AD12345",
| |
| "title":"ADTestTitle",
| |
| "adldx":"1",
| |
| "type":"preroll",
| |
| "length":"20"
| |
| },
| |
| }
| |
| </syntaxhighlight>
| |
| | |
| === Managing Ads ===
| |
| If there is an Ad block within the playing content (such as a midroll) you need to:
| |
| * Reset the playhead position to 0 for each ad.
| |
| * Call the '''adStop''' event at the end of each ad or increment the adldx
| |
| | |
| The Simplified API can automatically detect the change from ad to content as well as ad to ad if the assetID changes; however, there could be situations where the same ad is played back to back.
| |
| | |
| Sometimes it is not possible for integrators to provide different assetId value for individual ads in a sequence of ads. Taking this into account, the Simplified API will support a new parameter for ad metadata: '''adIdx'''. This parameter is an index of an individual ad in a sequence of ads. Once the next ad is started the adIdx parameter should be changed and provided as part of ad metadata.
| |
| You can either increment/change the adldx value, and/or call adStop at the end of each Ad.
| |
| | |
| <syntaxhighlight lang="swift"> | |
| // Example of passing both values
| |
| self.data.updateValue("adStop", forKey: "event")
| |
| self.data.updateValue("223", forKey: "adldx")
| |
| self.nielsenEventTracker.trackEvent(data)
| |
| </syntaxhighlight>
| |
| | |
| === Static Metadata ===
| |
| {| class="wikitable"
| |
| |-
| |
| ! Key !! Description !! Values !! Required
| |
| |-
| |
| | type || type identifier || <code> "static" </code> || Yes
| |
| |-
| |
| | assetid || unique ID assigned for each article/section || <code> "AID885-9984" </code> || Yes
| |
| |-
| |
| |section || Unique Value assigned to page/site section || <code> "homeSection" </code> || Yes
| |
| |-
| |
| | segA ||name of program (25 character limit); limit to 25 unique values across custom segments (segA + segB + segC) || <code> "CustomSegmentValueA" </code> || Yes
| |
| |-
| |
| | segB || custom segment B; limit to 25 unique values across custom segments (segA + segB + segC) || <code> "CustomSegmentValueB" </code> || No
| |
| |-
| |
| | segC || custom segment C; limit to 25 unique values across custom segments (segA + segB + segC) || <code> "CustomSegmentValueC" </code> || No
| |
| |-
| |
| |}
| |
| <syntaxhighlight lang="json">
| |
| {
| |
| "static":
| |
| {
| |
| "type": "static",
| |
| "section": "homeSection",
| |
| "assetid": "AID885-9984",
| |
| "segA": "CustomSegmentValueA",
| |
| "segB": "CustomSegmentValueB",
| |
| "segC": "CustomSegmentValueC",
| |
| }
| |
| },
| |
| </syntaxhighlight>
| |
| | |
| === Putting it all together ===
| |
| {{ExampleCode|
| |
| |Swift =
| |
| <syntaxhighlight lang="swift">
| |
| func loadPreRollAd() -> [String : Any] {
| |
|
| |
| //Loading Ad data
| |
|
| |
| url = NSURL(string: "http://www.nielseninternet.com/NWCC-3002/prog_index.m3u8")
| |
|
| |
| let content = [
| |
| "assetName":"Big Buck Bunny",
| |
| "assetid":"B66473",
| |
| "length":"3600",
| |
| "program":"MyProgram",
| |
| "segB":"CustomSegmentValueB",
| |
| "segC":"segmentC",
| |
| "title":"S2,E3",
| |
| "type":"content",
| |
| "section":"app_Mainpage",
| |
| "airdate":"20180120 10:00:00",
| |
| "isfullepisode":"y",
| |
| "crossId1":"Standard Episode ID",
| |
| "crossId2" :"Content Originator",
| |
| "adloadtype":"2"
| |
| ]
| |
|
| |
| let staticObj = [
| |
| "type":"static",
| |
| "section":"homeSection",
| |
| "segA":"CustomSegmentValueA",
| |
| "segB":"CustomSegmentValueB",
| |
| "segC":"CustomSegmentValueC"
| |
| ]
| |
| | |
| let ad = [
| |
| "assetid":"AD12345",
| |
| "title":"ADTestTitle",
| |
| "adldx":"1",
| |
| "type":"preroll",
| |
| "length":"20"
| |
| ]
| |
|
| |
| let metadata = [
| |
| "content" : content,
| |
| "ad" : ad,
| |
| "static" : staticObj
| |
| ] as [String : Any]
| |
|
| |
|
| |
| let data = [
| |
| "metadata" : metadata,
| |
| "event": "playhead",
| |
| "playheadPosition": "0",
| |
| "type": "ad",
| |
| ] as [String : Any]
| |
|
| |
| return data
| |
| }
| |
| </syntaxhighlight>
| |
| |Objective C = <syntaxhighlight lang="objective-c"> | |
| #import <Foundation/Foundation.h>
| |
| #import "SDKMethods.h"
| |
| | |
| @implementation SDKMethods
| |
| //Loading content Data
| |
| - (NSDictionary *)loadContentData
| |
| {
| |
| - (NSDictionary *)loadPreRollAd
| |
| {
| |
| self.url = [NSURL URLWithString:@"http://www.nielseninternet.com/NWCC-3002/prog_index.m3u8"];
| |
|
| |
| //We should pass content dictionary also in Ad video.
| |
| NSDictionary *content = @{ @"assetName":@"ChromeCast1",
| |
| @"assetid":@"C77664",
| |
| @"length":@"3600",
| |
| @"program":@"MyProgram",
| |
| @"segB":@"CustomSegmentValueB",
| |
| @"segC":@"segmentC",
| |
| @"title":@"S2,E3",
| |
| @"type":@"content",
| |
| @"section":@"app_Mainpage",
| |
| @"airdate":@"20180120 10:00:00",
| |
| @"isfullepisode":@"y",
| |
| @"adloadtype":@"2",
| |
| @"channelName":@"My Channel 1",
| |
| @"pipMode":@"false" };
| |
|
| |
| NSDictionary *ad = @{ @"assetid":@"AD12345",
| |
| @"title":@"ADTestTitle",
| |
| @"type":@"preroll",
| |
| @"length":@"20" };
| |
| | |
| NSDictionary *staticObj = @{ @"type":@"static",
| |
| @"section":@"homeSection",
| |
| @"segA":@"CustomSegmentValueA",
| |
| @"segB":@"CustomSegmentValueB",
| |
| @"segC":@"CustomSegmentValueC" };
| |
|
| |
| //static data should be empty in Ad video
| |
| NSDictionary *metadata = @{ @"content" : content,
| |
| @"ad" : ad,
| |
| @"static" : @staticObj };
| |
|
| |
| NSDictionary *data = @{ @"metadata" : metadata,
| |
| @"event": @"playhead",
| |
| @"type": @"ad",
| |
| @"playheadPosition": @"0" };
| |
|
| |
| return data;
| |
| }
| |
| </syntaxhighlight>
| |
| }}
| |
| | |
| == JSON examples ==
| |
| Additional JSON examples such as:
| |
| | |
| * [[Digital_Measurement_Simplified_API_Supplements#Static_Metadata|Static Metadata Only]]
| |
| * [[Digital_Measurement_Simplified_API_Supplements#Ad_Metadata|Ad Metadata Example]]
| |
| * [[Digital_Measurement_Simplified_API_Supplements#ID3_Payload|ID3 payload for DTVR]]
| |
| * [[Digital_Measurement_Simplified_API_Supplements#Pause_Event|Sample Pause Event]]
| |
| * [[Digital_Measurement_Simplified_API_Supplements#Complete_Event|Sample Complete Event]]
| |
| | |
| == Handling Foreground and Background states ==
| |
| For iOS, background/foreground detection is handled by the app lifecylce APIs which are provided by [https://developer.apple.com/library/content/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/TheAppLifeCycle/TheAppLifeCycle.html Apple:]
| |
| | |
| Foreground/Background state measurement is a requirement of Nielsen AppSDK implementation which is especially crucial for static measurement.
| |
| | |
| {{Template:iOS_Privacy_and_Opt-Out}}
| |
| | |
| == Example Code ==
| |
| === Putting it all together ===
| |
| The below code was built to show the functionality of the Nielsen Simplified API using a standard no-frills player. An Advanced Player is available with the SDK Bundle.
| |
| {{ExampleCode|
| |
| |Swift =
| |
| [[File:iphonescreenshot.png|thumb]]
| |
| === Swift Code Example ===
| |
| Select the below link to download the sample files <br>
| |
| [https://engineeringportal.nielsen.com/w/downloads/digital/sampleapplications/TrackEvent-SDKSwift.zip Download Project Files]
| |
| ==== NielsenInit.swift ====
| |
| | |
| <syntaxhighlight lang="swift">
| |
| // This is sample code of a very basic implementation of the Nielsen 'Simplified API'
| |
| // This code is for educational purposes only
| |
| //
| |
| import Foundation
| |
| import NielsenAppApi
| |
| | |
| class NielsenInit : NSObject {
| |
| class func createEventTracker(delegate: NielsenEventTrackerDelegate) -> NielsenEventTracker?{
| |
|
| |
| //Initialising the NielsenEventTracker class by passing app information which returns the instance of NielsenEventTracker.
| |
|
| |
| var nielsenEventTracker: NielsenEventTracker?
| |
|
| |
| let appInformation = [
| |
|
| |
| "appid": "PDA7D5EE6-B1B8-4123-9277-2A788XXXXXXX",
| |
| "appversion": "1.0",
| |
| "appname": "Amazing app",
| |
| "sfcode": "dcr",
| |
| "nol_devDebug": "DEBUG"
| |
| ]
| |
|
| |
| nielsenEventTracker = NielsenEventTracker(appInfo:appInformation, delegate:delegate)
| |
| return nielsenEventTracker
| |
| }
| |
| }
| |
| </syntaxhighlight>
| |
| | |
| ==== SDKMethods.swift ====
| |
| <syntaxhighlight lang="swift">
| |
| | |
| import Foundation
| |
| | |
| class SDKMethods : NSObject {
| |
|
| |
| var nielsenApi : NielsenAppApi!
| |
| var url = NSURL(string: "")
| |
| var content : NSDictionary!
| |
|
| |
| //Loading Static Data
| |
| func loadStatic() -> [String : Any] {
| |
|
| |
| let staticObj = [
| |
| "type":"static",
| |
| "section":"homeSection",
| |
| "segA":"CustomSegmentValueA",
| |
| "segB":"CustomSegmentValueB",
| |
| "segC":"CustomSegmentValueC"]
| |
| | |
|
| |
| let metadata = [
| |
| "content" : [String:String](),
| |
| "ad" : [String:String](),
| |
| "static" : staticObj ] as [String : Any]
| |
|
| |
| let data = [
| |
| "metadata" : metadata,
| |
| "event": "playhead",
| |
| "type": "static",
| |
| "playheadPosition": "0" ] as [String : Any]
| |
|
| |
| return data
| |
| }
| |
| | |
| //Loading content Data
| |
| func loadContent() -> [String : Any] {
| |
|
| |
| url = NSURL(string: "http://www.nielseninternet.com/NielsenConsumer/prog_index.m3u8")
| |
|
| |
| let content = [
| |
| "assetName":"ChromeCast1",
| |
| "assetid":"C77664",
| |
| "length":"3600",
| |
| "program":"MyProgram",
| |
| "segB":"CustomSegmentValueB",
| |
| "segC":"segmentC",
| |
| "title":"S2,E3",
| |
| "type":"content",
| |
| "section":"myApi_app",
| |
| "airdate":"20180120 10:00:00",
| |
| "isfullepisode":"y",
| |
| "adloadtype":"2",
| |
| "channelName":"My Channel 1",
| |
| "pipMode":"false" ]
| |
|
| |
| //Ad data,static data should be empty in content video dictionary
| |
| let metadata = [
| |
| "content" : content,
| |
| "ad" : [String:String](),
| |
| "static" : [String:String]() ] as [String : Any]
| |
|
| |
| let data = [
| |
| "metadata" : metadata,
| |
| "event": "playhead",
| |
| "type":"content",
| |
| "playheadPosition": "0" ] as [String : Any]
| |
|
| |
| return data
| |
| | |
| }
| |
| }
| |
| </syntaxhighlight>
| |
| ==== ViewController.swift ====
| |
| <syntaxhighlight lang="swift">
| |
| | |
| import UIKit
| |
| import AVKit
| |
| import CoreLocation
| |
| import AdSupport
| |
| import AVFoundation
| |
| import NielsenAppApi
| |
| | |
| class ViewController: UIViewController, NielsenEventTrackerDelegate, AVPlayerViewControllerDelegate {
| |
|
| |
| var videoType : Int!
| |
| var player : AVPlayer!
| |
| var playerController : AVPlayerViewController!
| |
| var sdkMethods : SDKMethods!
| |
| var nielsenEventTracker : NielsenEventTracker!
| |
|
| |
| var data : [String : Any]!
| |
| var timeObserver: Any!
| |
| var totalVideosPlayed = 0
| |
| var totalVideos : Int!
| |
|
| |
| override func viewDidLoad() {
| |
| super.viewDidLoad()
| |
|
| |
| //Mark: In NielsenInit class we are initialising the NielsenEventTracker.
| |
|
| |
| //Getting the instance of NielsenEventTracker
| |
| self.nielsenEventTracker = NielsenInit.createEventTracker(delegate: self)
| |
|
| |
| //Mark: In SDKMethods class we wrote methods which creates content,Ad objects
| |
| sdkMethods = SDKMethods()
| |
|
| |
| if(videoType == Constants.onlyContent){
| |
| //loading video content data
| |
| self.data = sdkMethods.loadContent()
| |
| }else{
| |
| //loading Ad data
| |
| self.data = sdkMethods.loadPreRollAd()
| |
| }
| |
|
| |
| setPlayer()
| |
| setPlayHeadPosition()
| |
|
| |
| //Setting observer to know the completion of video
| |
| setVideoFinishObserver()
| |
| }
| |
|
| |
| override func viewDidAppear(_ animated: Bool) {
| |
| //loading static data
| |
| let staticData = sdkMethods.loadStatic()
| |
|
| |
| //sending static data to SDK.
| |
| self.nielsenEventTracker.trackEvent(staticData)
| |
| }
| |
|
| |
| func setPlayer() {
| |
|
| |
| //creating player
| |
| player = AVPlayer.init(url: sdkMethods.url! as URL)
| |
| playerController = AVPlayerViewController()
| |
| playerController.view.frame = CGRect(x:0 , y:100, width: self.view.frame.width, height: 300)
| |
| playerController.player = player;
| |
| playerController.showsPlaybackControls = true;
| |
| playerController.delegate = self;
| |
|
| |
| //Adding observer to player to track play,pause and reverse
| |
| player.addObserver(self, forKeyPath: "rate", options: NSKeyValueObservingOptions.new, context: nil)
| |
| player.play()
| |
|
| |
| self.addChildViewController(playerController)
| |
| self.view.addSubview(playerController.view)
| |
| }
| |
|
| |
| func setPlayHeadPosition() {
| |
|
| |
| //Setting play head position
| |
| let timeInterval : CMTime = CMTimeMakeWithSeconds(1.0, 10)
| |
| playerController.player?.addPeriodicTimeObserver(forInterval: timeInterval, queue: DispatchQueue.main) {(elapsedTime: CMTime) -> Void in
| |
|
| |
| let time : Float64 = self.playerController.player!.currentTime().seconds;
| |
| let pos = Int64(time);
| |
| let playHeadPos = String(pos)
| |
|
| |
| //updating playHead position in dictionary.
| |
| self.data.updateValue(playHeadPos, forKey: "playheadPosition")
| |
|
| |
| //Sending data dictionary to SDK with updated playHead position.
| |
| self.nielsenEventTracker.trackEvent(self.data)
| |
| }
| |
| }
| |
|
| |
| func setVideoFinishObserver() {
| |
|
| |
| //observer fires on completion of Video
| |
| NotificationCenter.default.addObserver(self, selector: #selector(playerDidFinishPlaying), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: playerController.player?.currentItem)
| |
| }
| |
|
| |
| //rate 0.0 = Video Pause or stopped
| |
| //rate 1.0 = Video played or resumed
| |
| override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
| |
| if keyPath == "rate" {
| |
| if let rate = change?[NSKeyValueChangeKey.newKey] as? Float {
| |
|
| |
| if rate == 0.0 {
| |
| print("Playback stopped")
| |
|
| |
| //on video pause, updating event as pause in dictionary
| |
| self.data.updateValue("pause", forKey: "event")
| |
|
| |
| //sending the dictionary to SDK with "pause" event.
| |
| self.nielsenEventTracker.trackEvent(self.data)
| |
| }
| |
| if rate == 1.0 {
| |
| print("normal playback")
| |
|
| |
| //On Play resume setting event as Playhead
| |
| self.data.updateValue("playhead", forKey: "event")
| |
| }
| |
| }
| |
| }
| |
| }
| |
|
| |
| override func viewDidDisappear(_ animated: Bool) {
| |
|
| |
| //on moving to other screen, updating event as pause in dictionary
| |
| self.data.updateValue("pause", forKey: "event")
| |
|
| |
| player.rate = 0
| |
| player.pause()
| |
| }
| |
|
| |
| @objc func playerDidFinishPlaying(note: NSNotification) {
| |
|
| |
| self.player?.removeObserver(self, forKeyPath: "rate")
| |
|
| |
| //As 1 video completed playing, incrementing the variable value.
| |
| totalVideosPlayed += 1
| |
|
| |
| if(videoType == Constants.onlyContent || totalVideosPlayed == totalVideos){
| |
| //When content video completes or total videos finishs, let's send complete event to SDK
| |
| sendCompleteEventToSDK()
| |
| }else{
| |
| //On completion of Ad updating "adStop" event to SDK.
| |
| self.data.updateValue("adStop", forKey: "event")
| |
|
| |
| //sending "adStop" event to SDK.
| |
| self.nielsenEventTracker.trackEvent(data)
| |
| }
| |
|
| |
| //Checking if total videos played or not.
| |
| if(totalVideosPlayed != totalVideos){
| |
|
| |
| //Checking if videoType is contentWithOneAd, then after completion of Ad, will play the content video.
| |
| if(videoType == Constants.contentWithOneAd){
| |
|
| |
| //loading video content data
| |
| self.data = sdkMethods.loadContent()
| |
| }else if(videoType == Constants.contentWithTwoAds){
| |
| if(totalVideosPlayed == 1){
| |
|
| |
| //loading 2nd Ad data
| |
| self.data = sdkMethods.loadMidRollAd()
| |
| }else{
| |
| | |
| //loading video content data
| |
| self.data = sdkMethods.loadContent()
| |
| }
| |
| }
| |
| setPlayer()
| |
|
| |
| //Adding observer to player to check is buffering finished
| |
| self.timeObserver = player.addPeriodicTimeObserver(forInterval: CMTime(value: 1, timescale: 3), queue: DispatchQueue.main) { [weak self] time in
| |
|
| |
| //checking the video player status
| |
| self?.handlePlayerStatus(time: time)
| |
| self?.setPlayHeadPosition()
| |
|
| |
| //Setting observer to know the completion of video
| |
| self?.setVideoFinishObserver()
| |
|
| |
| }
| |
| }
| |
| }
| |
|
| |
| func handlePlayerStatus(time: CMTime) {
| |
| if player.status == .readyToPlay {
| |
|
| |
| // buffering is finished, setting event as Playhead
| |
| self.data.updateValue("playhead", forKey: "event")
| |
| player.removeTimeObserver(self.timeObserver)
| |
| }
| |
| if player.status == .unknown{
| |
| print("Buffering")
| |
| }
| |
| }
| |
|
| |
| func sendCompleteEventToSDK(){
| |
|
| |
| //onCompletion of video, updating event as complete in dictionary
| |
| self.data.updateValue("complete", forKey: "event")
| |
|
| |
| //sending the dictionary to SDK with "complete" event.
| |
| self.nielsenEventTracker.trackEvent(self.data)
| |
| }
| |
|
| |
| deinit {
| |
|
| |
| print("Remove NotificationCenter Deinit")
| |
| NotificationCenter.default.removeObserver(self)
| |
| }
| |
| }
| |
| | |
| | |
| </syntaxhighlight>
| |
| | |
| |Objective C =
| |
| [[File:iphonescreenshot.png|thumb]]
| |
| === Objective-C Code Example ===
| |
| Select the below link to download the sample files <br>
| |
| [https://engineeringportal.nielsen.com/w/downloads/digital/sampleapplications/TrackEvent-SDKObjC.zip Download Project Files]
| |
| ==== NielsenInit.m ====
| |
| <syntaxhighlight lang="Objective-C">
| |
| // NielsenInit.m
| |
| // VideoPlayerAppObjC
| |
| // This is sample code of a very basic implementation of the Nielsen 'Simplified API'
| |
| // This code is for educational purposes only
| |
| | |
| | |
| #import "NielsenInit.h"
| |
| #import <NielsenAppApi/NielsenEventTracker.h>
| |
| | |
| @implementation NielsenInit
| |
| | |
| + (NielsenEventTracker *)createNielsenEventTrackerWithDelegate:(id<NielsenEventTrackerDelegate>)delegate
| |
| {
| |
| //Initialising the NielsenEventTracker class by passing app information which returns the instance of NielsenEventTracker.
| |
|
| |
| NSDictionary *appInformation = @{ @"appid": @"PDA7D5EE6-B1B8-4123-9277-2A788BC6XXXX",
| |
| @"appversion": @"1.0",
| |
| @"appname": @"Abdul's Objc Test app",
| |
| @"sfcode": @"dcr",
| |
| @"ccode": @"123",
| |
| @"dma":@"456",
| |
| @"uoo":@"0",
| |
| @"nol_devDebug": @"INFO",
| |
| @"containerId": @"1" };
| |
|
| |
| return [[NielsenEventTracker alloc] initWithAppInfo:appInformation delegate:delegate];
| |
| }
| |
| | |
| | |
| @end
| |
| </syntaxhighlight>
| |
| | |
| ==== NielsenInit.h ====
| |
| <syntaxhighlight lang="Objective-C">
| |
| #import <Foundation/Foundation.h>
| |
| | |
| @class NielsenEventTracker;
| |
| @protocol NielsenEventTrackerDelegate;
| |
| | |
| @interface NielsenInit : NSObject
| |
| | |
| + (NielsenEventTracker *)createNielsenEventTrackerWithDelegate:(id<NielsenEventTrackerDelegate>)delegate;
| |
| | |
| @end
| |
| </syntaxhighlight>
| |
| | |
| ==== SDKMethods.m ====
| |
| <syntaxhighlight lang="Objective-C">
| |
| #import <Foundation/Foundation.h>
| |
| #import "SDKMethods.h"
| |
| | |
| | |
| @implementation SDKMethods
| |
| | |
| //Loading content Data
| |
| - (NSDictionary *)loadContentData
| |
| {
| |
|
| |
| self.url = [NSURL URLWithString:@"http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerEscapes.mp4"];
| |
|
| |
| NSDictionary *content = @{ @"assetName":@"ChromeCast1",
| |
| @"assetid":@"C77664",
| |
| @"length":@"3600",
| |
| @"program":@"MyProgram",
| |
| @"segB":@"CustomSegmentValueB",
| |
| @"segC":@"segmentC",
| |
| @"title":@"S2,E3",
| |
| @"type":@"content",
| |
| @"section":@"cloudApi_app",
| |
| @"airdate":@"20180120 10:00:00",
| |
| @"isfullepisode":@"y",
| |
| @"adloadtype":@"2",
| |
| @"channelName":@"My Channel 1",
| |
| @"pipMode":@"false" };
| |
|
| |
| //Ad data,static data should be empty in content video dictionary
| |
| NSDictionary *metadata = @{ @"content" : content,
| |
| @"ad" : @{},
| |
| @"static" : @{} };
| |
|
| |
| NSDictionary *data = @{ @"metadata" : metadata,
| |
| @"event": @"playhead",
| |
| @"type": @"content",
| |
| @"playheadPosition": @"0" };
| |
|
| |
|
| |
|
| |
| return data;
| |
| }
| |
| | |
| //Loading Ad data
| |
| - (NSDictionary *)loadPreRollAd
| |
| {
| |
| self.url = [NSURL URLWithString:@"http://www.nielseninternet.com/NWCC-3002/prog_index.m3u8"];
| |
|
| |
| //We should pass content dictionary also in Ad video.
| |
| NSDictionary *content = @{ @"assetName":@"ChromeCast1",
| |
| @"assetid":@"C77664",
| |
| @"length":@"3600",
| |
| @"program":@"MyProgram",
| |
| @"segB":@"CustomSegmentValueB",
| |
| @"segC":@"segmentC",
| |
| @"title":@"S2,E3",
| |
| @"type":@"content",
| |
| @"section":@"cloudApi_app",
| |
| @"airdate":@"20180120 10:00:00",
| |
| @"isfullepisode":@"y",
| |
| @"adloadtype":@"2",
| |
| @"channelName":@"My Channel 1",
| |
| @"pipMode":@"false" };
| |
|
| |
| NSDictionary *ad = @{ @"assetid":@"AD12345",
| |
| @"title":@"ADTestTitle",
| |
| @"type":@"preroll",
| |
| @"length":@"20" };
| |
|
| |
| //static data should be empty in Ad video
| |
| NSDictionary *metadata = @{ @"content" : content,
| |
| @"ad" : ad,
| |
| @"static" : @{} };
| |
|
| |
| NSDictionary *data = @{ @"metadata" : metadata,
| |
| @"event": @"playhead",
| |
| @"type": @"ad",
| |
| @"playheadPosition": @"0" };
| |
|
| |
| return data;
| |
| }
| |
| | |
| @end
| |
| | |
| </syntaxhighlight>
| |
| | |
| ==== SDKMethods.h ====
| |
| <syntaxhighlight lang="Objective-C">
| |
| #import <Foundation/Foundation.h>
| |
| | |
| @interface SDKMethods : NSObject
| |
| | |
| @property(nonatomic, strong) NSURL *url;
| |
| | |
| - (NSDictionary *)loadContentData;
| |
| - (NSDictionary *)loadPreRollAd;
| |
| | |
| @end
| |
| </syntaxhighlight>
| |
| | |
| ==== ViewController.m ====
| |
| <syntaxhighlight lang="Objective-C">
| |
| #import "ViewController.h"
| |
| #import "NielsenInit.h"
| |
| #import "SDKMethods.h"
| |
| #import <MediaPlayer/MediaPlayer.h>
| |
| #import <AVKit/AVKit.h>
| |
| #import "Constants.h"
| |
| | |
| #import <NielsenAppApi/NielsenEventTracker.h>
| |
| | |
| NSMutableDictionary *mutableData;
| |
| NSDictionary *data;
| |
| SDKMethods *sdkMethods;
| |
| AVPlayer *player;
| |
| AVPlayerViewController *playerController;
| |
| NielsenEventTracker *nielsenEventTracker;
| |
| | |
| int totalVideosPlayed = 0;
| |
| id timeObserver;
| |
| | |
| @interface ViewController()<AVPlayerViewControllerDelegate>
| |
| | |
| @end
| |
| | |
| @implementation ViewController
| |
| | |
| - (void)viewDidLoad {
| |
| [super viewDidLoad];
| |
|
| |
| //Mark: In NielsenInit class we are initialising the NielsenEventTracker.
| |
|
| |
| //Getting the instance of NielsenEventTracker
| |
| nielsenEventTracker = [NielsenInit createNielsenEventTrackerWithDelegate:nil];
| |
|
| |
| //Mark: In SDKMethods class we wrote methods which creates content,Ad objects
| |
| sdkMethods = [[SDKMethods alloc] init];
| |
|
| |
| if(self.videoType == onlyContent){
| |
| //loading video content data
| |
| data = [sdkMethods loadContentData];
| |
| }else{
| |
| //loading Ad data
| |
| data = [sdkMethods loadPreRollAd];
| |
| }
| |
|
| |
| //Converting "data" to mutable dictionary as we have to update playhead, event values.
| |
| mutableData =[data mutableCopy];
| |
|
| |
| [self setPlayer];
| |
| [self setPlayHeadPosition];
| |
|
| |
| //Setting observer to know the completion of video
| |
| [self setVideoFinishObserver];
| |
| }
| |
| | |
| -(void) setPlayer {
| |
|
| |
| //creating player
| |
| player = [AVPlayer playerWithURL:[sdkMethods url]];
| |
| playerController = [[AVPlayerViewController alloc] init];
| |
| playerController.view.frame = CGRectMake(0,100,self.view.frame.size.width,300);
| |
| playerController.player = player;
| |
| playerController.showsPlaybackControls = YES;
| |
| playerController.delegate = self;
| |
|
| |
| //Adding observer to player to track play,pause and reverse
| |
| [player addObserver:self
| |
| forKeyPath:@"rate"
| |
| options:(NSKeyValueObservingOptionNew)
| |
| context:nil];
| |
|
| |
| [player play];
| |
|
| |
| [self addChildViewController:playerController];
| |
| [self.view addSubview:playerController.view];
| |
| }
| |
| | |
| -(void) setPlayHeadPosition {
| |
|
| |
| //Setting play head position
| |
| CMTime timeInterval = CMTimeMakeWithSeconds(1, 1);
| |
| [player addPeriodicTimeObserverForInterval:(timeInterval) queue:dispatch_get_main_queue() usingBlock:^(CMTime time){
| |
| NSTimeInterval seconds = CMTimeGetSeconds(time);
| |
| NSInteger intSec = seconds;
| |
| NSString* strSec = [NSString stringWithFormat:@"%li", intSec];
| |
|
| |
| //updating playHead position in dictionary.
| |
| [mutableData setValue:strSec forKey:@"playheadPosition"];
| |
|
| |
| //Sending data dictionary to SDK with updated playHead position.
| |
| [nielsenEventTracker trackEvent:mutableData];
| |
| }];
| |
| }
| |
| | |
| - (void) setVideoFinishObserver {
| |
|
| |
| //observer fires on completion of Ad
| |
| [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(itemDidFinishPlaying:) name:AVPlayerItemDidPlayToEndTimeNotification object:playerController.player.currentItem];
| |
| }
| |
| | |
| //rate 0.0 = Video Pause or stopped
| |
| //rate 1.0 = Video played or resumed
| |
| //rate -1.0 = Play reversed/rewind.
| |
| - (void)observeValueForKeyPath:(NSString *)keyPath
| |
| ofObject:(id)object
| |
| change:(NSDictionary *)change
| |
| context:(void *)context
| |
| {
| |
| if (object == player && [keyPath isEqualToString:@"rate"]) {
| |
| NSNumber * newValue = [change objectForKey:NSKeyValueChangeNewKey];
| |
| int intValue = newValue.intValue;
| |
| if(intValue == 0){
| |
| NSLog(@"playback paused");
| |
|
| |
| //on video pause, updating event as pause in dictionary
| |
| [mutableData setValue:@"pause" forKey:@"event"];
| |
|
| |
| //sending the dictionary to SDK with "pause" event.
| |
| [nielsenEventTracker trackEvent:mutableData];
| |
|
| |
| }else if(intValue == 1){
| |
| NSLog(@"Normal playback");
| |
|
| |
| //On Play resume setting event as Playhead
| |
| [mutableData setValue:@"playhead" forKey:@"event"];
| |
|
| |
| }
| |
| }
| |
| }
| |
| | |
| - (void)viewDidDisappear:(BOOL)animated
| |
| {
| |
| //on moving to other screen, updating event as pause in dictionary
| |
| [mutableData setValue:@"pause" forKey:@"event"];
| |
|
| |
| //As it is a pause event setting the playheadPosition to empty.
| |
| [mutableData setValue:@"" forKey:@"playheadPosition"];
| |
|
| |
| player.rate = 0;
| |
| [player pause];
| |
|
| |
| [super viewDidDisappear:animated];
| |
| }
| |
| | |
| | |
| -(void)itemDidFinishPlaying:(NSNotification *) notification {
| |
|
| |
| [player removeObserver:self forKeyPath:@"rate"];
| |
|
| |
| [self sendCompleteEventToSDK];
| |
|
| |
| //As 1 video completed playing, incrementing the variable value.
| |
| totalVideosPlayed += 1;
| |
|
| |
| //Checking if total videos played or not.
| |
| if(totalVideosPlayed != self.totalVideos){
| |
|
| |
| //Checking if videoType is contentWithAd, then after completion of Ad, will play the content video.
| |
| if(self.videoType == contentWithAd){
| |
|
| |
| //loading video content data
| |
| data = [sdkMethods loadContentData];
| |
|
| |
| mutableData =[data mutableCopy];
| |
|
| |
| [self setPlayer];
| |
|
| |
| //Adding observer to player to check is buffering finished
| |
| CMTime timeInterval = CMTimeMakeWithSeconds(1, 3);
| |
| timeObserver = [player addPeriodicTimeObserverForInterval:(timeInterval) queue:dispatch_get_main_queue() usingBlock:^(CMTime time){
| |
|
| |
| //checking the video player status
| |
| [self handlePlayerStatus:time];
| |
| [self setPlayHeadPosition];
| |
| //Setting observer to know the completion of video
| |
| [self setVideoFinishObserver];
| |
|
| |
| }];
| |
|
| |
| }
| |
| }
| |
| }
| |
| | |
| - (void) handlePlayerStatus : (CMTime) time {
| |
|
| |
| if(player.status == AVPlayerItemStatusReadyToPlay){
| |
|
| |
| // buffering is finished, setting event as Playhead
| |
| [mutableData setValue:@"playhead" forKey:@"event"];
| |
| [player removeTimeObserver:timeObserver];
| |
| }
| |
| }
| |
| | |
| - (void) sendCompleteEventToSDK {
| |
|
| |
| //onCompletion of video, updating event as complete in dictionary
| |
| [mutableData setValue:@"complete" forKey:@"event"];
| |
|
| |
| //As it is a complete event setting the playheadPosition to empty.
| |
| [mutableData setValue:@"" forKey:@"playheadPosition"];
| |
|
| |
| //sending the dictionary to SDK with "complete" event.
| |
| [nielsenEventTracker trackEvent:mutableData];
| |
| }
| |
| | |
| - (void)dealloc {
| |
| NSLog(@"Remove NotificationCenter dealloc");
| |
| [[NSNotificationCenter defaultCenter] removeObserver:self];
| |
| }
| |
| @end
| |
| </syntaxhighlight>
| |
| | |
| ==== ViewController.h ====
| |
| <syntaxhighlight lang="Objective-C">
| |
| #import <UIKit/UIKit.h>
| |
| | |
| @class NielsenEventTracker;
| |
| @protocol NielsenEventTrackerDelegate;
| |
| | |
| | |
| @interface ViewController : UIViewController
| |
| | |
| @property (nonatomic) int videoType;
| |
| @property (nonatomic) int totalVideos;
| |
| | |
| @end
| |
| </syntaxhighlight>
| |
| | |
| ==== OptOutVC.m ====
| |
| <syntaxhighlight lang="Objective-C">
| |
| #import "OptOutVC.h"
| |
| #import "NielsenInit.h"
| |
| | |
| | |
| #import <NielsenAppApi/NielsenEventTracker.h>
| |
| | |
| @interface OptOutVC ()
| |
| | |
| @property (weak, nonatomic) IBOutlet UIWebView *webView;
| |
| | |
| @end
| |
| | |
| @implementation OptOutVC
| |
| | |
| - (void)viewDidLoad {
| |
| [super viewDidLoad];
| |
|
| |
| self.nielsenEventTracker = [NielsenInit createNielsenEventTrackerWithDelegate:nil];
| |
|
| |
| //Getting the optPut URL from eventTracker
| |
| [self.webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:self.nielsenEventTracker.optOutURL]]];
| |
| }
| |
| | |
| @end
| |
| </syntaxhighlight>
| |
| | |
| ==== OptOutVC.h ====
| |
| <syntaxhighlight lang="Objective-C">
| |
| | |
| #import <UIKit/UIKit.h>
| |
| | |
| @class NielsenEventTracker;
| |
| @protocol NielsenEventTrackerDelegate;
| |
| | |
| @interface OptOutVC : UIViewController
| |
| | |
| @property (nonatomic, weak) NielsenEventTracker *nielsenEventTracker;
| |
| | |
| @end
| |
| </syntaxhighlight>
| |
| | |
| }}
| |