Digital Measurement iOS Simplified API: Difference between revisions

From Engineering Client Portal

(removed "containerId" information from chart)
 
(32 intermediate revisions by 4 users not shown)
Line 1: Line 1:
{{Breadcrumb|}} {{Breadcrumb|Digital}} {{Breadcrumb|DCR & DTVR}}  {{CurrentBreadcrumb}}
{{Breadcrumb|}} {{Breadcrumb|Digital}} {{Breadcrumb|Simplified SDK API}}  {{CurrentBreadcrumb}}
[[Category:Digital]]
[[Category:Digital]]


== Overview ==
== Introduction ==
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.
This document outlines the features of the new Simplified Nielsen SDK, which is available for use within your APP or online video site, which simplifies the integration of Nielsen measurement for Digital Content Ratings or DTVR. You are only required to execute one of 4 events.  Tell the SDK if you want to track 'content', an 'ad', or a 'static' page load.  If an event is sent out of sequence, this new API will put it in the correct order for processing. All of the Standard SDK calls can be replaced with one API call, and one dictionary object, where any value could be another complex dictionary object.<br/>
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]]), [[Digital Ad Ratings]] (DAR), [[Digital Audio]]. Nielsen SDKs are also equipped to measure static content and can track key life cycle events of an application like:
<b>NOTE:</b> use of simplified API is not recommended for BrowserSDK implementations at this time.
*Application launch events and how long app was running
*Time of viewing a sub section / page in the application.
 
__TOC__


== Prerequisites ==
== Prerequisites ==
Line 18: Line 14:
Refer to [[Digital Measurement Onboarding]] guide for information on how to get a Nielsen App SDK and appid.
Refer to [[Digital Measurement Onboarding]] guide for information on how to get a Nielsen App SDK and appid.


== Simplified SDK API ==
== 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.  
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]]
[[File:SimplifiedAPI_vs_StandardAPI_New.jpg|2048px|link=https://engineeringportal.nielsen.com/w/images/9/91/SimplifiedAPI_vs_StandardAPI_New.jpg]]
Line 27: Line 23:


''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.''
''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.''
__TOC__


==  Implementation ==
==  Implementation ==
Line 40: Line 38:


=== How to obtain the NielsenAppApi.Framework ===
=== How to obtain the NielsenAppApi.Framework ===
The Nielsen AppSDK can either be downloaded directly or can be integrated directly within an application through the use of a CocoaPod or Gradle.
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]]
* [[Special:Downloads|Select to Download Directly]]
* [[Digital_Measurement_iOS_Artifactory_Guide|Select to obtain Cocoapod implementation guide]]
* [[Digital_Measurement_iOS_Artifactory_Guide|Select to obtain Cocoapod implementation guide]]


=== Configuring Xcode Development Environment ===
=== 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 support 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
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>
<blockquote>'''Note''': All communications between the SDK and the Census (Collection Facility) use HTTPS.</blockquote>
Line 70: Line 68:


== SDK Initialization ==
== 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. (Version 4.0 for Android)
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.  


* A maximum of four SDK instances per appid are supported. When a fifth SDK instance is launched, the SDK will return “nil” from [[initWithAppInfo:delegate:]]
* 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 following table contains the list of arguments that should be passed during initialization.
Line 82: Line 80:
|-
|-
| appid || Unique id for the application assigned by Nielsen.
| appid || Unique id for the application assigned by Nielsen.
It is GUID data type, provided by the Nielsen Technical Account Manager (TAM).  
It is GUID data type, provided by the Nielsen<br> Technical Account Manager (TAM).  
|| Nielsen-specified || Yes || PXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
|| Nielsen-specified || Yes || <code>PXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX</code>
|-
|-
| appname || Name of the application || Client-defined || No|| "Nielsen Sample App"
| appname || Name of the application || Client-defined || No|| "Nielsen Sample App"
Line 89: Line 87:
| appversion || Current version of the app used || Client-defined || No || "1.0.2"
| appversion || Current version of the app used || Client-defined || No || "1.0.2"
|-
|-
| sfcode || Specifies the Nielsen collection facility to which the SDK should connect
| sfcode || Specifies the Nielsen collection<br> facility to which the SDK should connect
'''DTVR'''
'''DTVR'''
* "us"
* "us"
Line 98: Line 96:
|| Nielsen-specified || Yes || "dcr"
|| Nielsen-specified || Yes || "dcr"
|-
|-
|nol_devDebug || Enables Nielsen console logging and is only required for testing ||Nielsen-specified || Optional || "DEBUG"
|nol_devDebug || Enables Nielsen console logging and is only required for testing ||Nielsen-specified || Optional || <code>"DEBUG"
"INFO"<br>
"WARN"
|}
|}
<br />
<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 ====
==== Sample SDK Initialization Code ====
{{ExampleCode|
{{ExampleCode|
|Swift  =  
|Swift  =  
Swift 4.0 Example:
Swift Example:
<code>NielsenInit.swift</code>
 
<syntaxhighlight lang="swift">
<syntaxhighlight lang="swift">
NielsenInit.swift


import Foundation
import Foundation
Line 126: Line 138:
             "appname": "Amazing app",
             "appname": "Amazing app",
             "sfcode": "dcr",
             "sfcode": "dcr",
             "nol_devDebug": "DEBUG",
             "nol_devDebug": "DEBUG"
            "containerId": String(containerId)  //Keep container id unique constant, you can use tag property of player.
         ]
         ]
          
          
Line 168: Line 179:
     NSDictionary *appInformation = @{ @"appid": @"PDA7D5EE6-B1B8-4123-9277-2A788XXXXXXX",
     NSDictionary *appInformation = @{ @"appid": @"PDA7D5EE6-B1B8-4123-9277-2A788XXXXXXX",
                             @"appversion": @"1.0",
                             @"appversion": @"1.0",
                             @"appname": @"Objc Test app",
                             @"appname": @"Amazing app",
                             @"sfcode": @"dcr",
                             @"sfcode": @"dcr",
                             @"nol_devDebug": @"INFO",
                             @"nol_devDebug": @"DEBUG"};
                            @"containerId": @"1" }; //Keep container id unique constant, you can use tag property of player.
                       
   
     return [[NielsenEventTracker alloc] initWithAppInfo:appInformation delegate:delegate];
     return [[NielsenEventTracker alloc] initWithAppInfo:appInformation delegate:delegate];
}
}
</syntaxhighlight>
The following would be the <code>NielsenInit.h</code> file:
<syntaxhighlight lang="objective-c">
#import <Foundation/Foundation.h>
@class NielsenEventTracker;
@protocol NielsenEventTrackerDelegate;
@interface NielsenInit : NSObject
+ (NielsenEventTracker *)createNielsenEventTrackerWithDelegate:(id<NielsenEventTrackerDelegate>)delegate;
@end
</syntaxhighlight>
</syntaxhighlight>


Line 197: Line 192:
     nielsenEventTracker = [NielsenInit createNielsenEventTrackerWithDelegate:nil];
     nielsenEventTracker = [NielsenInit createNielsenEventTrackerWithDelegate:nil];
/////
/////
-(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];
    }];
}
}
</syntaxhighlight>
</syntaxhighlight>
Line 272: Line 253:
  "playheadPosition":<playhead value | Unix Timestamp>,
  "playheadPosition":<playhead value | Unix Timestamp>,
  "id3Data": <id3 payload>,
  "id3Data": <id3 payload>,
"ottData": <ott info object>,
"optout": <optout string>
}
}
</syntaxhighlight>
</syntaxhighlight>
Line 286: Line 269:
|-
|-
|'''pause'''||
|'''pause'''||
This event should be used to in the following cases:
This event should be used to in the following cases:<br>
application enters background,
* application enters background,<br>
any application interruptions,
* any application interruptions,<br>
content playback is paused. (Pause is detected by SDK automatically only if the time gap between commands exceeds 30 minutes.)
* content playback is paused.<br>
(Pause is detected by SDK automatically only if the time gap between commands exceeds 30 minutes.)
|-
|-
|'''complete'''||
|'''complete'''||
This event should be called when the session is completed, ends, or the user initiates a stop.
This event should be called when the session is completed/ends. It should also be used with DCR Static measurement when a new view event is desired even though previous static metaData had the same section parameter.
|-
|-
|'''adStop'''||
|'''adStop'''||
Line 880: Line 864:
* Call the '''adStop''' event at the end of each ad or increment the adldx
* Call the '''adStop''' event at the end of each ad or increment the adldx


The Simplified SDK 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.
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.
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.
Line 1,041: Line 1,025:
Additional JSON examples such as:
Additional JSON examples such as:


* [[Digital_Measurement_Simplified_SDK_Supplements#Static_Metadata|Static Metadata Only]]
* [[Digital_Measurement_Simplified_API_Supplements#Static_Metadata|Static Metadata Only]]
* [[Digital_Measurement_Simplified_SDK_Supplements#Ad_Metadata|Ad Metadata Example]]
* [[Digital_Measurement_Simplified_API_Supplements#Ad_Metadata|Ad Metadata Example]]
* [[Digital_Measurement_Simplified_SDK_Supplements#ID3_Payload|ID3 payload for DTVR]]
* [[Digital_Measurement_Simplified_API_Supplements#ID3_Payload|ID3 payload for DTVR]]
* [[Digital_Measurement_Simplified_SDK_Supplements#Pause_Event|Sample Pause Event]]
* [[Digital_Measurement_Simplified_API_Supplements#Pause_Event|Sample Pause Event]]
* [[Digital_Measurement_Simplified_SDK_Supplements#Complete_Event|Sample Complete Event]]
* [[Digital_Measurement_Simplified_API_Supplements#Complete_Event|Sample Complete Event]]


== Handling Foreground and Background states ==
== Handling Foreground and Background states ==
Line 1,052: Line 1,036:
Foreground/Background state measurement is a requirement of Nielsen AppSDK implementation which is especially crucial for static measurement.
Foreground/Background state measurement is a requirement of Nielsen AppSDK implementation which is especially crucial for static measurement.


== Privacy and Opt-Out ==
{{Template:iOS_Privacy_and_Opt-Out}}
There are three primary methods for implementing user Opt-out preferences:
# '''[[#OS-level_Opt-out|OS-level Opt-out]]''' - managed by ''Limit Ad Tracking'' setting on device ('''preferred approach''').
# '''[[#Legacy_Opt-out|Legacy Opt-out]]''' - Direct call to SDK; used only for older versions of Nielsen iOS SDK (< 5.1.1.18)
# '''[[#App_Level_Opt_Out|App Level Opt-Out]]''' - Where [https://developer.apple.com/documentation/adsupport Ad Framework] cannot be leveraged


=== OS-level Opt-out ===
== Viewability and Audibility Implementation ==
''OS-level Opt-out'' method available on Nielsen iOS '''SDK Versions 5.1.1.18 and above'''.
Viewability metrics allow AppSDK to track the visibility of the player and collect information about how much the player container is visible to the end user while playback is occurring.  


The Nielsen SDK automatically leverages the iOS's ''Limit Ad Tracking'' setting. The user is opted out of demographic measurement if the OS-level ''"Limit Ad Tracking"'' ("Limit Ad Tracking" for Android) setting is ''enabled''. As a publisher, you cannot override this setting.
The viewability pings will be fired following the same rules as measurement pings. Those pings will be POST requests, not GET requests like other data pings. POST body for viewability requests will contain the key-value pairs in JSON format. The key parameters in the URL schemes are '''invs, inau, inss, invp''' and '''ines''' which will contain the collected viewability data. This data will be formatted according to the specific rules so that downstream it will be possible to match measurement and viewability data for a session.


=== Legacy Opt-out ===
Audibility metrics will capture the volume level as well as mute/unmute state of the device while playback is is occurring.
The ''Legacy opt-out'' method is only necessary for Nielsen iOS '''SDK versions less than 5.1.1.18'''.


Nielsen iOS SDK 5.1.1.17 and above will check for ''OS-level opt-out'' first, if available. The user will be opted out if indicated at the OS-level '''OR''' the App-level.
=== Data Collected ===
 
{| class="wikitable"
==== The legacy opt-out method works as follows: ====
|-
* Get the legacy Nielsen opt-out URL via [[optOutURL]]
! Parameter !! Description
* Display a WebView element whose loadUrl is set to the value obtained from [[optOutURL]]
|-
* Detect if the WebView URL changes to a special URL that indicates Opt-in, or Opt-out and close the WebView
| Measured Value || Value is different for different request parameters:
** Opt-out if the WebView URL = <code>nielsenappsdk://1</code>
{| class="wikitable"
** Opt-in if the WebView URL = <code>nielsenappsdk://0</code>
|-
* Pass the detected URL to the [[userOptOut]] function
| '''invs''' ||  Intersection ratio for the target view in percent (from 0 to 100). Default threshold for this value is 5. Example: ['''50''',1,1528457356,10]
** Example: <syntaxhighlight lang=swift>NielsenAppApi?.userOptOut("nielsenappsdk://1"); // User opt-out</syntaxhighlight>
|-
 
| '''inau''' || Volume level on the device in percent (from 0 to 100), where 0 - mute, 100 - max volume level. Default threshold for this value is 1. Example: ['''30''',1,1528457356,10]
==== Legacy Opt-out example code ====
|-
| '''inss''' || Device screen size as "WxH", where W - is width in pixels, H - is height in pixels. Example: ['''"1024x768"''',1,1528457356,10]
|-
| '''invp''' || Current window size. It is different than the device screen size in a multiple scene mode or on a desktop. Format is "WxH", where W - is width in pixels, H - is height in pixels. Example: ['''"800x600"''',1,1528457356,10]
|-
| '''ines''' || Player view size as "WxH", where W - is width in pixels, H - is height in pixels. Example: ['''"300x200"''',1,1528457356,10]
|}
|-
| Start offset || Value contains the first playhead or the first id3 offset with non-null CID after start, flush or resume. Example playhead: [50,'''1''',1528457356,10]. Example id3 offset: [50,'''70100''',1528457356,10]
|-
| Start timestamp || Timestamp value when the time period related to this time series item was started. Example: [50,1,'''1528457356''',10]
|-
| Duration || Duration value is calculated as a difference between the last playhead and the first playhead for the current time series item. Example: [50,1,1528457356,'''10''']
|}


Viewability support requires additional parameters to be provided from player applications to the SDK. In order to provide those parameters and to start the viewability measurement the following method is added to the public SDK API (please refer to the [[trackViewability|trackViewability public API reference]] for the details and usage examples):
<br/><br/>
'''Objective C'''
<syntaxhighlight lang="objective-c">
- (void)trackViewability:(nonnull NSDictionary *)data;
</syntaxhighlight>
<br/>
'''Swift'''
<syntaxhighlight lang="swift">
<syntaxhighlight lang="swift">
var webView: WKWebView!
func trackViewability(_ data: [String : Any])
var NIELSEN_URL_OPT_OUT : String = "nielsenappsdk://1"
var NIELSEN_URL_OPT_IN : String = "nielsenappsdk://0"
 
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
 
      let urlStr = navigationAction.request.url?.absoluteString
 
        if(urlStr == NIELSEN_URL_OPT_OUT || urlStr == NIELSEN_URL_OPT_IN){
            let appApi = self.nielsenApi
            appApi?.userOptOut(urlStr)
            decisionHandler(.allow)
 
        }else{
          decisionHandler(.cancel)
        }
    }
</syntaxhighlight>
</syntaxhighlight>


=== Retrieve current Opt-Out preference ===
<br/>
Whether the user is opted out viaOS-level Opt-out or via App-level Opt-out, the current Opt-Out status as detected by the SDK is available via the [[optOutStatus]] property in the Nielsen SDK API
For Audibility measurent SDK uses iOS system API in order to get an volume level for the device:
 
=== Required Privacy Links ===
Users must either have access to the "About Nielsen Measurement" page, or have similar text available within the native app. Include "About Nielsen Measurement" and "Your Choices" link in the Privacy Policy / EULA or as a button near the link to the app's Privacy Policy.
 
In addition, the following text must be included in your app store description.
<blockquote>
'''"Please note: This app features Nielsen’s proprietary measurement software which contributes to market research, like Nielsen’s TV Ratings. Please see http://priv-policy.imrworldwide.com/priv/mobile/us/en/optout.html for more information"'''</blockquote>
 
==== Webview Example  ====
The below code is an example of displaying the Nielsen Privacy page to the user.
<syntaxhighlight lang="swift">
<syntaxhighlight lang="swift">
import UIKit
AVAudioSession.sharedInstance().outputVolume
import WebKit
import NielsenAppApi
 
class OptOutVC: UIViewController, NielsenAppApiDelegate, WKNavigationDelegate {
   
    var webView: WKWebView!
    var nielsenApi: NielsenAppApi!
   
    override func loadView() {
        webView = WKWebView()
        webView.navigationDelegate = self
        view = webView
    }
   
    override func viewDidLoad() {
        super.viewDidLoad()
        self.view.backgroundColor = UIColor(patternImage: UIImage(named: "new_ios_bg.png")!)
        self.nielsenApi = NielsenInit.createNielsenApi(delegate: self)
       
        if let appApi = self.nielsenApi {
            //Getting the optPut URL from SDK
            if let url = URL(string: appApi.optOutURL) {
                webView.load(URLRequest(url: url))
                webView.allowsBackForwardNavigationGestures = true
            }
        }
    }
   
}
</syntaxhighlight>
</syntaxhighlight>
<br>
=== App Level Opt Out ===
This is only used if the Ad Framework is not available.  The Opt-Out occurs by opening a Nielsen-defined web page and passing the user choice from the 'WebView'. In order to do this, the application needs to:
* Implement the UIWebView delegate method to open the Nielsen Privacy web page
* Capture user's selection
* Pass the selection back to the SDK via the <code>userOptOut</code>.
==== Capture and forward user selection ====
<syntaxhighlight lang="objective-c">-(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
    NSString *command = [NSString stringWithFormat:@”%@”,request.URL];
    if ([command isEqualToString:kNielsenWebClose]) {
        // Close the WebView
        [self performSelector:@selector(closeOptOutView) withObject:nil afterDelay:0];
        return NO;
    }
    // Retrieve next URL if it’s not opt-in/out selection
    return (![nAppApiObject userOptOut:command]);
}</syntaxhighlight>


*The app gets the user selection string via webviews shouldStartLoadWithRequest and invokes <code>userOptOut</code> with user selection. The delegate method handles the 'WebView' URL requests, interprets the commands, and calls the SDK accordingly.
**<code>[nAppApiObject userOptOut:command]</code> passes the user's selection on Nielsen Privacy page to the SDK to allow the SDK to perform the required functions.
<blockquote>'''Note:''' When 'WebView' is closed, pass the status returned from 'WebView' to the SDK within the app. The App SDK manages the user's choice (Opt-Out / Opt-In), the app does not need to manage this status.</blockquote>


== Example Code ==
== Example Code ==
Line 1,173: Line 1,096:
|Swift  =  
|Swift  =  
[[File:iphonescreenshot.png|thumb]]
[[File:iphonescreenshot.png|thumb]]
=== Swift Version 4 Code Example ===
=== Swift Code Example ===
Select the below link to download the sample files <br>
Select the below link to download the sample files <br>
[https://engineeringportal.nielsen.com/w/downloads/digital/sampleapplications/TrackEvent-SDKSwift.zip Download Project Files]
[https://engineeringportal.nielsen.com/w/downloads/digital/sampleapplications/TrackEvent-SDKSwift.zip Download Project Files]
Line 1,198: Line 1,121:
             "appname": "Amazing app",
             "appname": "Amazing app",
             "sfcode": "dcr",
             "sfcode": "dcr",
             "nol_devDebug": "DEBUG",
             "nol_devDebug": "DEBUG"
            "containerId": String(containerId)
         ]
         ]
          
          
Line 1,521: Line 1,443:
     //Initialising the NielsenEventTracker class by passing app information which returns the instance of NielsenEventTracker.
     //Initialising the NielsenEventTracker class by passing app information which returns the instance of NielsenEventTracker.
      
      
     NSDictionary *appInformation = @{ @"appid": @"PDA7D5EE6-B1B8-4123-9277-2A788BC653CA",
     NSDictionary *appInformation = @{ @"appid": @"PDA7D5EE6-B1B8-4123-9277-2A788BC6XXXX",
                             @"appversion": @"1.0",
                             @"appversion": @"1.0",
                             @"appname": @"Abdul's Objc Test app",
                             @"appname": @"Abdul's Objc Test app",

Latest revision as of 20:14, 21 April 2023

Engineering Portal / Digital / Simplified SDK API / Digital Measurement iOS Simplified API

Introduction

This document outlines the features of the new Simplified Nielsen SDK, which is available for use within your APP or online video site, which simplifies the integration of Nielsen measurement for Digital Content Ratings or DTVR. You are only required to execute one of 4 events. Tell the SDK if you want to track 'content', an 'ad', or a 'static' page load. If an event is sent out of sequence, this new API will put it in the correct order for processing. All of the Standard SDK calls can be replaced with one API call, and one dictionary object, where any value could be another complex dictionary object.
NOTE: use of simplified API is not recommended for BrowserSDK implementations at this time.

Prerequisites

To start using the App SDK, the following details are required:

  • App ID (appid): Unique ID assigned to the player/site and configured by product.
  • 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. 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.

Co-Existance.jpg

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 (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: [AdSupport, JavascriptCore, SystemConfiguration, Security, AVFoundation, libc++]

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 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.

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

Note: All communications between the SDK and the Census (Collection Facility) use HTTPS.

Download Framework

The first step is to download and copy the 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 Objective-C into Swift

Using Objective-C

Add the code to the View Controller’s header file.

#import <NielsenAppApi/NielsenEventTracker.h>

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).
Parameter / Argument Description Source Required? Example
appid Unique id for the application assigned by Nielsen.

It is GUID data type, provided by the Nielsen
Technical Account Manager (TAM).

Nielsen-specified Yes PXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
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
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 "DEBUG"

"INFO"
"WARN"


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 "nol_devDebug": "INFO" , 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.

Note: DO NOT activate the Debug flag in a production environment.


Sample SDK Initialization Code

Swift

Swift Example:

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
    }
}


Sample code: ViewController
ViewController.swift

    override func viewDidLoad() {
        super.viewDidLoad()
        
        //Getting the instance of NielsenEventTracker
        
        self.nielsenEventTracker = NielsenInit.createEventTracker(delegate: self)

Objective C

Initialize the Nielsen App object within the viewDidLoad view controller delegate method using initWithAppInfo:delegate:

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.

Objective-C Example: NielsenInit.m

    
#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];
}

The ViewController.m file could then contain the following line(s):

  
    //Getting the instance of NielsenEventTracker
    nielsenEventTracker = [NielsenInit createNielsenEventTrackerWithDelegate:nil];
/////

}



APP SDK Error & Event Codes

To view the Error and Event codes for iOS and Android, please review the 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:

 
- (void)trackEvent:(NSDictionary *)data;

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:

{ 
 "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>
}


Event Types

The New API method supports the following event types:

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:

  • application enters background,
  • any application interruptions,
  • content playback is paused.

(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. It should also be used with DCR Static measurement when a new view event is desired even though previous static metaData had the same section parameter.

adStop

This event should be called at the end of each ad and is required when advertisements have the same assetId.


DCR and DTVR require various levels of data. Please select the tab for the product you are interested in reviewing.

DCR

Digital Content Ratings

Parameter Description Supported values Example
event Event identifier

String: playhead, pause, complete, adStop

"event":"playhead"
type Determines the metadata object

that should be used for crediting.

String:
content, ad, static

"type":"content"
metadata Object that holds metadata values of specific types

Detailed in tables below

Object
"metadata":{ 
   "content": <content metadata object>,
   "ad": <ad metadata object>,
   "static": <static metadata object>
 },
playheadPosition Playhead value as reported by video player. Unix timestamp (seconds since Jan-01-1970 UTC) for live video. String

Position value is Unix timestamp (live): "playheadPosition":"1542797780"

Position value is playhead:

"playheadPosition":"10"

Content Metadata

Content metadata sent for every playheadPosition update.

Key Description Example Required
assetName name of program (100 character limit) "MyTest789" Yes
assetid unique ID assigned to asset "B66473" Yes
length length of content in seconds "3600" (0 for live stream or unknown) Yes
program name of program (100 character limit) "MyProgram" Yes
segB custom segment B ¹ "CustomSegmentValueB" No
segC custom segment C ¹ "segmentC" No
title name of program (100 character limit) "S2,E3" Yes
type 'content', 'ad', 'static' 'content' Yes
section Unique Value assigned to page/site section "HomePage" Yes
airdate the airdate in the linear TV ² "20180120 10:00:00" Yes
isfullepisode full episode flag "y"- full episode, "n"- not a full episode Yes
crossId1 standard episode ID "Standard Episode ID" Yes
crossId2 content originator (only required for distributors) Provided by Nielsen Yes (if distributor)
adloadtype linear ("1") vs dynamic ("2") ad model "1" Yes

¹ Custom segments (segB and segC) can be used to aggregate video and/or static content within a single brand to receive more granular reports.
² Acceptable Air Date Formats:

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


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.

{ 
"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": "",
}

DTVR

Digital TV Ratings info

Parameter Description Supported values Example
event Event identifier

String: playhead,pause,complete, adStop

"event":"playhead"
type Determines the metadata object that should be used for crediting.

String:
content, ad, static

"type":"content"
metadata Object that holds metadata values of specific types.

Detailed in tables below

Object
"metadata":{ 
   "content": <content metadata object>,
   "ad": <ad metadata object>,
   "static": <static metadata object>
 },
playheadPosition Playhead value or Unix timestamp String

Position value is Unix timestamp: "playheadPosition":"1501225191747"

Position value is playhead:

"playheadPosition":"10"

id3Data Nielsen ID3 payload String

"id3Data": "www.nielsen.com/065H2g6E7ZyQ5UdmMAbbpg ==/_EMc37zfVgq_8KB7baUYfg==/ADQCAmgV1Xyvnynyg60 kZO_Ejkcn2KLSrTzyJpZZ-QeRn8GpMGTWI7-HrEKzghxyzC yBEoIDv2kA2g1QJmeYOl5GnwfrLDVK2bNLTbQxr1z9VBfxa hBcQP5tqbjhyMzdVqrMKuvvJO1jhtSXa9AroChb11ZUnG1W VJx2O4M=/33648/22847/00"

Content Metadata

Content metadata sent for every playheadPosition update.

Key Description Example Required
channelName name of program (32 character limit) "MyTest789" Yes
type 'content', 'ad', 'static' "content" Yes
adModel linear ("1") vs dynamic ("2") ad model "1" Yes

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.

{ 
"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"
}

DCR & DTVR

Applies to DCR and DTVR

Parameter Description Supported values Example
event Event identifier

String: playhead, pause, complete, adStop

"event":"playhead"
type Determines the metadata object that should be used for crediting.

String:
content, ad, static

"type":"content"
metadata Object that holds metadata values of specific types

Detailed in tables below

Object
"metadata":{ 
   "content": <content metadata object>,
   "ad": <ad metadata object>,
   "static": <static metadata object>
 },
playheadPosition Playhead value or Unix timestamp (seconds since Jan-01-1970 UTC) String

Position value is Unix timestamp: "playheadPosition":"1501225191747"

Position value is playhead:

"playheadPosition":"10"

id3Data Nielsen ID3 payload Object

"id3Data": "www.nielsen.com/065H2g6E7ZyQ5UdmMAbbpg ==/_EMc37zfVgq_8KB7baUYfg==/ADQCAmgV1Xyvnynyg60 kZO_Ejkcn2KLSrTzyJpZZ-QeRn8GpMGTWI7-HrEKzghxyzC yBEoIDv2kA2g1QJmeYOl5GnwfrLDVK2bNLTbQxr1z9VBfxa hBcQP5tqbjhyMzdVqrMKuvvJO1jhtSXa9AroChb11ZUnG1W VJx2O4M=/33648/22847/00"

ottData Object that holds OTT information Object
"ottData": {
   "ottStatus": 1,
   "ottType": casting,
   "ottDevice": chromecast,
   "ottDeviceID": 1234
}

Content Metadata

Content metadata sent for every playheadposition update.

Keys Description Example Required
length length of content in seconds seconds (0 for live stream) Yes
type "content", "ad", "static" "content" Yes
adModel linear vs dynamic ad model 1=Linear

2=Dynamic Ads

custom
adloadtype DCR Ad Model 1=Linear

2=Dynamic Ads

custom

+ 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.
++ Acceptable Air Date Formats:

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


Below is a sample event for DCR/DTVR joint integration. If no ad or static values, these can be left as blank/null.

{ 
"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"
}

Ad Metadata

The ad metadata (if applicable) should be passed for each individual ad, if ads are available during or before the stream begins.

Key Description Example Required
assetid unique ID assigned to ad (no Special Characters) "AD12345" Yes
title unique name assigned to ad "ADtitle" No
adldx ad index (See the "Managing Ads" section below) "66478364" Yes
type type of ad ("preroll", "midroll", or "postroll") "preroll" Yes
length length of ad, in seconds "20" No

Ad Metadata Sample

{
  "ad": {
    "assetid":"AD12345",
    "title":"ADTestTitle",
    "adldx":"1",
    "type":"preroll",
    "length":"20"
  },
}

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.

            // Example of passing both values
            self.data.updateValue("adStop", forKey: "event")
            self.data.updateValue("223", forKey: "adldx")
            self.nielsenEventTracker.trackEvent(data)

Static Metadata

Key Description Values Required
type type identifier "static" Yes
assetid unique ID assigned for each article/section "AID885-9984" Yes
section Unique Value assigned to page/site section "homeSection" Yes
segA name of program (25 character limit); limit to 25 unique values across custom segments (segA + segB + segC) "CustomSegmentValueA" Yes
segB custom segment B; limit to 25 unique values across custom segments (segA + segB + segC) "CustomSegmentValueB" No
segC custom segment C; limit to 25 unique values across custom segments (segA + segB + segC) "CustomSegmentValueC" No
{
    "static":
            {
                "type": "static",
                "section": "homeSection",
                "assetid": "AID885-9984",
                "segA": "CustomSegmentValueA",
                "segB": "CustomSegmentValueB",
                "segC": "CustomSegmentValueC",
            }
        },

Putting it all together

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    
    }

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;
}


JSON examples

Additional JSON examples such as:

Handling Foreground and Background states

For iOS, background/foreground detection is handled by the app lifecylce APIs which are provided by Apple:

Foreground/Background state measurement is a requirement of Nielsen AppSDK implementation which is especially crucial for static measurement.

Privacy and Opt-Out

There are currently 3 flavors of the Nielsen SDK. Please check the "Implementation" section for a comparison of the three flavors. Implementing opt-out for the three flavors are different:

  1. Global iOS SDK Opt-out - managed by AppTracking or Limit Ad Tracking setting on device.
  2. Global iOS SDK No Ad Framework Opt-out - Direct call to SDK. Can be used without the Ad Framework.
  3. Global iOS SDK No ID Opt-out - Direct call to SDK. Should be used for Kids Category.

Global iOS SDK Opt-out

OS-level Opt-out method available on Nielsen iOS

The Nielsen SDK automatically leverages the iOS's Limit Ad Tracking or AppTracking setting.

  • If the User's device is running < iOS 13.x, the Nielsen SDK will check the status of Limit Ad Tracking.
  • iOS14 modifies the way Apple manages the collection of a User's Opt-In status through AppTracking. Starting with Version 8.x+, the Nielsen App SDK will check the iOS version during initialization. If the device is running iOS12 or iOS13, the Limit Ad Tracking setting is requested. If iOS14.x +, then AppTracking is utilized.

Webview Element

It is a requirement to display a WebView element whose loadUrl is set to the value obtained from optOutURL. If using the Global iOS SDK, this optOutURL informs the user how to deactivate/activate “App Tracking/Limit Ad Tracking”.


If you are implementing on AppleTV here are your Opt Out verbiage options : https://engineeringportal.nielsen.com/docs/DCR_Video_%26_Static_CTV_Device_SDK_Privacy

Sample Code for Global Build

Swift
import UIKit
import WebKit
import NielsenAppApi

class OptOutVC: UIViewController, NielsenAppApiDelegate, WKNavigationDelegate {
    var nielsenApi : NielsenAppApi!
    var webView: WKWebView!
   
    override func loadView() {
        webView = WKWebView()
        webView.navigationDelegate = self
        view = webView
    }

    override func viewDidLoad() {
        super.viewDidLoad()

    if let appApi = self.nielsenApi {
            //Getting the optPut URL from SDK
            if let url = URL(string: appApi.optOutURL) {
                webView.load(URLRequest(url: url))
                webView.allowsBackForwardNavigationGestures = true
            }}}

        func closeOptOutView() {
            self.dismiss(animated: true, completion: nil)
        }}
Objective-C
#import "OptOutVC.h"
#import "NielsenInit.h"
#import <NielsenAppApi/NielsenAppApi.h>

@interface OptOutVC ()

@property (weak, nonatomic) IBOutlet UIWebView *webView;
@end

@implementation OptOutVC

- (void)viewDidLoad {
    [super viewDidLoad];

- (void)viewDidLoad {
    [super viewDidLoad];
    //Getting the optPut URL from eventTracker
    [self.webView loadRequest:[NSURLRequest requestWithURL:[NSURL
 URLWithString:self.nielsenApi.optOutURL]]];
}}

Global iOS SDK No Ad Framework Opt-out

The User Choice method can be used without the Ad Framework, or in situations where the publisher does not wish to use the App Tracking Transparency Framework. As this flavor of the Nielsen SDK does not use the Ad Framework, so it is necessary to display an Optout Page to the user and capture their selection.

Similar to the Global iOS SDK Flavor, it is a requirement to display a WebView element whose loadUrl is set to the value obtained from optOutURL. This is a special URL that indicates Opt-in, or Opt-out and close the WebView. The steps are as follows:

  • Get the Nielsen opt-out URL via optOutURL
  • Display a WebView element whose loadUrl is set to the value obtained from optOutURL
  • Detect if the WebView URL changes to a special URL that indicates Opt-in, or Opt-out and close the WebView
    • Opt-out if the WebView URL = nielsenappsdk://1
    • Opt-in if the WebView URL = nielsenappsdk://0
  • Pass the detected URL to the userOptOut function
    • Example:
      NielsenAppApi?.userOptOut("nielsenappsdk://1"); // User opt-out
      

Sample code for No Ad Framework Build

Swift
import UIKit
import WebKit
import NielsenAppApi

class OptOutVC: UIViewController, NielsenAppApiDelegate, WKNavigationDelegate {
    var nielsenApi : NielsenAppApi!
    var webView: WKWebView!
   
    override func loadView() {
        webView = WKWebView()
        webView.navigationDelegate = self
        view = webView
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        if let appApi = self.nielsenApi {
            //Getting the optPut URL from SDK
            if let url = URL(string: appApi.optOutURL) {
                webView.load(URLRequest(url: url))
                webView.allowsBackForwardNavigationGestures = true
            }}}

        func closeOptOutView() {
            self.dismiss(animated: true, completion: nil)
        }

        func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: 
@escaping (WKNavigationActionPolicy) -> Void) {
            print(navigationAction.request.url?.absoluteString as Any) //For debugging to check what is being passed from webpage.
            if navigationAction.request.url?.absoluteString == "nielsen://close" {
                closeOptOutView()
                decisionHandler(.cancel)
            } else {
                if let url = navigationAction.request.url?.absoluteString, url.hasPrefix("nielsen") {
                    nielsenApi?.userOptOut(url). //either nielsenappsdk://1 or nielsenappsdk://0
                    decisionHandler(.cancel)
                } else {
                    if navigationAction.navigationType == .linkActivated {
                        if let url = navigationAction.request.url?.absoluteString, url.hasSuffix("#") {
                            decisionHandler(.allow)
                        } else {
                            decisionHandler(.cancel)
                            webView.load(navigationAction.request)
                        }
                    } else {
                        decisionHandler(.allow)
                    }}}}

}
Objective-C
#import "OptOutVC.h"
#import "NielsenInit.h"
#import <NielsenAppApi/NielsenAppApi.h>

@interface OptOutVC ()

@property (weak, nonatomic) IBOutlet UIWebView *webView;
@end

@implementation OptOutVC

- (void)viewDidLoad {
    [super viewDidLoad];

- (void)viewDidLoad {
    [super viewDidLoad];
    //Getting the optPut URL from eventTracker
    [self.webView loadRequest:[NSURLRequest requestWithURL:[NSURL
 URLWithString:self.nielsenApi.optOutURL]]];
}
     
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction
 decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
{
    if ([navigationAction.request.URL.absoluteString isEqualToString:kNielsenWebClose])
    {   [self performSelector:@selector(closeOptOutView) withObject:nil afterDelay:0];
        decisionHandler(WKNavigationActionPolicyCancel); 
     } else {
        if ([navigationAction.request.URL.absoluteString hasPrefix:@"nielsen"])
        {[self.nielsenAppApi userOptOut:navigationAction.request.URL.absoluteString];
            decisionHandler(WKNavigationActionPolicyCancel); 
        } else {
            if (navigationAction.navigationType == WKNavigationTypeLinkActivated) 
            { if ([navigationAction.request.URL.absoluteString hasSuffix:@"#"]) 
                      {decisionHandler(WKNavigationActionPolicyAllow);
                } else {
                    decisionHandler(WKNavigationActionPolicyCancel);
                    [webView loadRequest:[NSURLRequest requestWithURL:navigationAction.request.URL]];
                }} else {
                 decisionHandler(WKNavigationActionPolicyAllow);
            }}}
}


Global iOS SDK No ID Opt-out

If you are building an app that will be listed in the Kids Category:

  1. Ensure that you are using the NoID version of the Nielsen SDK Framework.
  2. Immediately following the initialization of the Nielsen SDK ensure you call the userOptOut API with Opt out selection:
NielsenAppApi?.userOptOut("nielsenappsdk://1"); // User opt-out

Retrieve current Opt-Out preference

Whether the user is opted out via OS-level Opt-out or via User Choice Opt-out, the current Opt-Out status as detected by the SDK is available via the optOutStatus property in the Nielsen SDK API

@property (readonly) BOOL optOutStatus

Viewability and Audibility Implementation

Viewability metrics allow AppSDK to track the visibility of the player and collect information about how much the player container is visible to the end user while playback is occurring.

The viewability pings will be fired following the same rules as measurement pings. Those pings will be POST requests, not GET requests like other data pings. POST body for viewability requests will contain the key-value pairs in JSON format. The key parameters in the URL schemes are invs, inau, inss, invp and ines which will contain the collected viewability data. This data will be formatted according to the specific rules so that downstream it will be possible to match measurement and viewability data for a session.

Audibility metrics will capture the volume level as well as mute/unmute state of the device while playback is is occurring.

Data Collected

Parameter Description
Measured Value Value is different for different request parameters:
invs Intersection ratio for the target view in percent (from 0 to 100). Default threshold for this value is 5. Example: [50,1,1528457356,10]
inau Volume level on the device in percent (from 0 to 100), where 0 - mute, 100 - max volume level. Default threshold for this value is 1. Example: [30,1,1528457356,10]
inss Device screen size as "WxH", where W - is width in pixels, H - is height in pixels. Example: ["1024x768",1,1528457356,10]
invp Current window size. It is different than the device screen size in a multiple scene mode or on a desktop. Format is "WxH", where W - is width in pixels, H - is height in pixels. Example: ["800x600",1,1528457356,10]
ines Player view size as "WxH", where W - is width in pixels, H - is height in pixels. Example: ["300x200",1,1528457356,10]
Start offset Value contains the first playhead or the first id3 offset with non-null CID after start, flush or resume. Example playhead: [50,1,1528457356,10]. Example id3 offset: [50,70100,1528457356,10]
Start timestamp Timestamp value when the time period related to this time series item was started. Example: [50,1,1528457356,10]
Duration Duration value is calculated as a difference between the last playhead and the first playhead for the current time series item. Example: [50,1,1528457356,10]

Viewability support requires additional parameters to be provided from player applications to the SDK. In order to provide those parameters and to start the viewability measurement the following method is added to the public SDK API (please refer to the trackViewability public API reference for the details and usage examples):

Objective C

- (void)trackViewability:(nonnull NSDictionary *)data;


Swift

func trackViewability(_ data: [String : Any])


For Audibility measurent SDK uses iOS system API in order to get an volume level for the device:

AVAudioSession.sharedInstance().outputVolume


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.

Swift

iphonescreenshot.png

Swift Code Example

Select the below link to download the sample files
Download Project Files

NielsenInit.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
    }
}

SDKMethods.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

  }
}

ViewController.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)
    }
}

Objective C

iphonescreenshot.png

Objective-C Code Example

Select the below link to download the sample files
Download Project Files

NielsenInit.m

//  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

NielsenInit.h

#import <Foundation/Foundation.h>

@class NielsenEventTracker;
@protocol NielsenEventTrackerDelegate;

@interface NielsenInit : NSObject

+ (NielsenEventTracker *)createNielsenEventTrackerWithDelegate:(id<NielsenEventTrackerDelegate>)delegate;

@end

SDKMethods.m

#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

SDKMethods.h

#import <Foundation/Foundation.h>

@interface SDKMethods : NSObject

@property(nonatomic, strong) NSURL *url;

- (NSDictionary *)loadContentData;
- (NSDictionary *)loadPreRollAd;

@end

ViewController.m

#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

ViewController.h

#import <UIKit/UIKit.h>

@class NielsenEventTracker;
@protocol NielsenEventTrackerDelegate;


@interface ViewController : UIViewController

@property (nonatomic) int videoType;
@property (nonatomic) int totalVideos;

@end

OptOutVC.m

#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

OptOutVC.h

#import <UIKit/UIKit.h>

@class NielsenEventTracker;
@protocol NielsenEventTrackerDelegate;

@interface OptOutVC : UIViewController

@property (nonatomic, weak) NielsenEventTracker *nielsenEventTracker;

@end