Digital Measurement Android Simplified API: Difference between revisions

From Engineering Client Portal

No edit summary
 
(4 intermediate revisions by the same user not shown)
Line 195: Line 195:
|-
|-
|'''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 921: Line 921:


== Privacy and Opt-Out ==
== Privacy and Opt-Out ==
There are two primary methods for implementing user Opt-out preferences:
There are two methods for implementing user Opt-out preferences in event tracker implementations:
# '''[[#OS-level_Opt-out|OS-level Opt-out]]''' - managed by ''Opt out of Ads Personalization'' setting on device ('''preferred approach''').
# '''[[#OS-level_Opt-out|OS-level Opt-out]]''' - managed by ''Opt out of Ads Personalization'' setting on device ('''preferred approach''').
# '''[[#Legacy_Opt-out|Legacy Opt-out]]''' - Direct call to SDK; used only for older versions of Nielsen Android SDK versions (< 5.1.1.18)
# '''[[#Event Tracker Opt-out|Event Tracker Opt-out]]''' - Tell SDK directly of opt-out status via event tracker; used when device-level setting is not available


=== OS-level Opt-out ===
=== OS-level Opt-out ===
Line 930: Line 930:
The Nielsen SDK automatically leverages the Android's ''Opt out of Ads Personalization'' setting. The user is opted out of demographic measurement if the OS-level ''"Opt out of Ads Personalization"'' ("Limit Ad Tracking" for iOS) setting is ''enabled''. As a publisher, you cannot override this setting.
The Nielsen SDK automatically leverages the Android's ''Opt out of Ads Personalization'' setting. The user is opted out of demographic measurement if the OS-level ''"Opt out of Ads Personalization"'' ("Limit Ad Tracking" for iOS) setting is ''enabled''. As a publisher, you cannot override this setting.


=== Legacy Opt-out ===
=== Event Tracker Opt-out ===
The ''Legacy opt-out'' method is only necessary for Nielsen Android '''SDK versions less than 5.1.1.18'''.
The ''Event Tracker Opt-out'' method is used when the application cannot rely on OS-level opt-out status.


Nielsen Android SDK 5.1.1.18 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.
Nielsen Android SDK 5.1.1.18 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 via track event call.


==== The legacy opt-out method works as follows: ====
==== The Event Tracker opt-out method works as follows: ====
* Get the current Nielsen opt-out URL via [[userOptOutURLString()]]
* Get the current Nielsen opt-out URL via [[userOptOutURLString()]]
* Display a WebView element whose loadUrl is set to the value obtained from [[userOptOutURLString()]]
* Display a WebView element whose loadUrl is set to the value obtained from [[userOptOutURLString()]]
Line 941: Line 941:
** Opt-out if the WebView URL = <code>nielsenappsdk://1</code>
** Opt-out if the WebView URL = <code>nielsenappsdk://1</code>
** Opt-in if the WebView URL = <code>nielsenappsdk://0</code>
** Opt-in if the WebView URL = <code>nielsenappsdk://0</code>
* Pass the detected URL to the [[userOptOut()]] function
*OR - in case or Amazon FireTV or where a WebView cannot be used, create an opt-out button near your App's privacy policy or Nielsen verbiage
** Example: <syntaxhighlight lang=java>appSdk.userOptOut("nielsenappsdk://1"); // User opt-out</syntaxhighlight>
*Update out-out status as shown below
* Example (user opted out): <syntaxhighlight lang=java>trackEvent(
{
    "optout":"nielsenappsdk://1"
})  // User opt-out</syntaxhighlight>
* Example (user opted in): <syntaxhighlight lang=java>trackEvent(
{
    "optout":"nielsenappsdk://0"
})  // User opt-in</syntaxhighlight>
 
==== Event Tracker Opt-out example code ====


==== Legacy Opt Out example code ====
===== Opt-out can be passed with other event data=====
<syntaxhighlight lang="java">
<syntaxhighlight lang="java">
public class OptOutActivity extends AppCompatActivity implements IAppNotifier {
trackEvent(
{
    "event": "playhead",
    "type": "content",
    "metadata":
    {
        "content":
        {
            "type":"content",
            "assetid":"assetid1",
            "isfullepisode":"y",
            "program":"MyProgram",
            "title":"S2,E3",
            "length":"3600",
            "airdate":"2016-02-20",
            "adloadtype":"1",
            ...
        }
    },
    "playheadPosition": "5",
    "optout":"nielsenappsdk://1"
})
</syntaxhighlight>


WebView webView;
===== Opt-out can be passed alone in separate trackEvent call=====
AppSdk appSdk;
<syntaxhighlight lang="java">
 
trackEvent(
  private static final String NIELSEN_URL_OPT_OUT = "nielsenappsdk://1";
{
  private static final String NIELSEN_URL_OPT_IN = "nielsenappsdk://0";
    "optout":"nielsenappsdk://1"
})
//  Within your app you would provide your User the option to Opt Out.
//  Perhaps via a toggle or button
//  This is separate from Limit Ad Tracking
 
      let urlStr = navigationAction.request.url?.absoluteString
 
        if(urlStr == NIELSEN_URL_OPT_OUT || urlStr == NIELSEN_URL_OPT_IN){
            let appApi = self.nielsenApi
            appApi?.userOptOut(urlStr)
}
</syntaxhighlight>
</syntaxhighlight>


Line 972: Line 994:
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.
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  ====
==== Webview Example  ====
The below code is an example of displaying the Nielsen Privacy page to the user.
The below code is an example of displaying the Nielsen Privacy page to the user.

Latest revision as of 20:11, 21 April 2023

Engineering Portal / Digital / Simplified SDK API / Digital Measurement Android 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 Android using Android Studio.

Setting up your Development Environment

Configuring Android Development Environment'

  • The Nielsen App SDK (located in the Downloads section of the website) class is the primary application interface to the Nielsen App SDK on Android.
  • The Nielsen App SDK class is defined as the only public class belonging to the com.nielsen.app.sdk package.

Nielsen App SDK is compatible with Android OS versions 2.3+. Clients can control / configure the protocol to be used – HTTPS or HTTP to suit their needs.

The requirement for the Java AppSdk.jar library and the libAppSdk.so native library will depend on the type of host application that will make use of them.

  • For Video player applications
    • The Android OS hosting the App SDK should use a media player supporting HLS streaming (Android 3.0 and later will support it natively).
    • If the player application uses a 3rd party media player implementing its own HLS, then the minimum Android version will be limited to version 2.3, since the SDK depends on Google Play support to work properly.
  • For Audio player applications
    • The Android OS hosting the App SDK should be at version 2.3 and later since the SDK depends on the Google Play support to work properly.

Once SDK is downloaded ensure to unzip the Nielsen SDK and copy the AppSdk.jar in your app (Android Studio) libs folder, then right click the AppSdk.jar and select Add As Library. Ensure the AppSdk.jar file is added in 'build.grade (App Level) file.

  • App SDK 1.2 provides support for x86, mips, and armeabi-7a architecture.

Google Play Services

Add the Google Play Services in the project, Steps: Android Studio -> File -> Project Structure ->(In module selection) select App -> Dependencies (tab) -> Click “+” button and select “com.google.android.gms:play-services”. Ensure it is added in build.gradle (App level) file

Manifest File

  • Add the following permissions on the project’s AndroidManifest.xml file.
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" android:required="false" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.INTERNET"/>

For more details to handle runtime permissions in Android versions, please visit [1].

  • In AndroidManifest.xml under <application> node add the following metadata
<meta-data 
android:name="com.google.android.gms.version" 
android:value="@integer/google_play_services_version"/>
  • App SDK checks to see if there is a Google service available and updated.
  • If not available or updated, App SDK will not use this service when executing its functions and will make reference to missing imports and the app will not be compiled.

Library

Nielsen App SDK uses the following packages/classes from the Google Play service.

  • google-play-services_lib

Classes/package

  • com.google.android.gms.ads.identifier.AdvertisingIdClient;
  • com.google.android.gms.ads.identifier.AdvertisingIdClient.Info;
  • com.google.android.gms.common.ConnectionResult;
  • com.google.android.gms.common.GooglePlayServicesUtil;
  • com.google.android.gms.common.GooglePlayServicesRepairableException;
  • com.google.android.gms.common.GooglePlayServicesNotAvailableException;

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)

  • 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:

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"



Sample SDK Initialization Code

Initialization of App SDK object through a JSON object

  
import android.content.Context;
import com.nielsen.app.sdk.IAppNotifier;
import com.nielsen.app.sdk.NielsenEventTracker;
import org.json.JSONException;
import org.json.JSONObject;

public class NielsenInit {
    private NielsenEventTracker mEventTracker = null;
    public NielsenEventTracker initEventTracker(Context mContext, IAppNotifier appNotifier){

        try {
            //Initialising the NielsenEventTracker class by passing app information which returns the instance of NielsenEventTracker.

            JSONObject appInformation = new JSONObject()

                    .put("appid", "PDA7D5EE6-B1B8-4123-9277-2A788XXXXXXX")
                    .put("appversion", "1.0")
                    .put("appname", "Android Test app")
                    .put("sfcode", "dcr")
                    .put("nol_devDebug", "INFO");

            mEventTracker = new NielsenEventTracker(mContext, appInformation, appNotifier);
        } catch (JSONException e) {
            e.printStackTrace();
        }

        return mEventTracker;
    }
}

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(JSONObject 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 | UTC>,
 "id3Data": <id3 payload>,
}


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 (seconds since Jan-1-1970 UTC) 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 time gap between commands in more than 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 of 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-1-1970 UTC) for live video. String

Position value is Unix timestamp (live):

"playheadPosition":"1501225191747"

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 "2" 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 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

   //Loading Ad data
    public JSONObject loadPreRollAdData(){
        JSONObject data = null;

        url = "http://www.nielseninternet.com/NWCC-3002/prog_index.m3u8";

        try {
            //We should pass content dictionary also in Ad video.
            JSONObject content = new JSONObject()
                    .put( "assetName","ChromeCast1")
                    .put( "assetid","C77664")
                    .put( "length","3600")
                    .put( "program","MyProgram")
                    .put( "segB","CustomSegmentValueB")
                    .put( "segC","segmentC")
                    .put( "title","S2,E3")
                    .put( "type","content")
                    .put( "section","app_Mainpage")
                    .put( "airdate","20180207 10:00:00")
                    .put( "isfullepisode","y")
                    .put( "adloadtype","2");

            JSONObject ad = new JSONObject()
                    .put("type", "static")
                    .put("assetid", "AD12345")
                    .put("title", "ADTestTitle")
                    .put("type", "preroll")
                    .put("length", "20");

            JSONObject staticObj = new JSONObject()
                    .put("type","static")
                    .put("section","homeSection")
                    .put("segA","CustomSegmentValueA")
                    .put("segB","CustomSegmentValueB")
                    .put("segC","CustomSegmentValueC");

            JSONObject metaData = new JSONObject()
                    .put("content", content)
                    .put("ad", ad)
                    .put("static", staticObj);

            data = new JSONObject()
                    .put("metadata", metaData)
                    .put("event", "playhead")
                    .put("type", "ad")
                    .put("playheadPosition", "0");

        } catch (JSONException e) {
            e.printStackTrace();
        }

        return data;
    }
}

JSON examples

Additional JSON examples such as:

Handling Foreground and Background states

Foreground/Background state measurement is a requirement of Nielsen AppSDK implementation which is especially crucial for static measurement. It may be implemented in multiple ways for Android. This includes

  • Enable the Nielsen SDK to measure background/foreground state by makingthe relevant update to the AndroidManifest.
  • Integrate Nielsen’s SdkBgFgDetectionUtility class within your Custom Application Class.
  • Custom implementation of the required methods within your application.

ForeGround/Background Measurement via AndroidManifest

The simplest way to measure the app background/foreground state is to add the following application tag to the Manifest XML. Integrating this into the Manifest XML will enable the SDK to measure app state directly. This approach is supported for Android 4.0 and up only; it requires that the application class is not in use for some other purpose.

<application android:name="com.nielsen.app.sdk.AppSdkApplication">

Using the Android SdkBgFbDetectionUtility Class

For developers who are already using the application class, it is recommended that background/foreground state is implemented using the SdkBgFgDetectionUtility class. The SdkBgFgDetectionUtility class is compatible with Android 4+ and has been made available to Nielsen clients.

Manual Background/ForeGround State Management

In cases where the developer is not able to use the AndroidManifest.xml solution nor the Nielsen provided SdkBgFgDetectionUtility class the developer will need to manually identify the change of state through the application and call the respective API (appInForeground() or appInBackground()) to inform the SDK regarding the change of state from background to foreground or foreground to background.

The SDK is informed about app state using the below methods.

AppLaunchMeasurementManager.appInForeground(getApplicationContext());
AppLaunchMeasurementManager.appInBackground(getApplicationContext());

Within the lifecycle of individual activities, onResume() and onPause() are best suited to providing indication of the app state.


Correct measurement of the foreground/background state is crucial to Static App measurement within Nielsen Digital Content Ratings (DCR).

Privacy and Opt-Out

There are two methods for implementing user Opt-out preferences in event tracker implementations:

  1. OS-level Opt-out - managed by Opt out of Ads Personalization setting on device (preferred approach).
  2. Event Tracker Opt-out - Tell SDK directly of opt-out status via event tracker; used when device-level setting is not available

OS-level Opt-out

OS-level Opt-out method available on Nielsen Android SDK Versions 5.1.1.18 and above.

The Nielsen SDK automatically leverages the Android's Opt out of Ads Personalization setting. The user is opted out of demographic measurement if the OS-level "Opt out of Ads Personalization" ("Limit Ad Tracking" for iOS) setting is enabled. As a publisher, you cannot override this setting.

Event Tracker Opt-out

The Event Tracker Opt-out method is used when the application cannot rely on OS-level opt-out status.

Nielsen Android SDK 5.1.1.18 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 via track event call.

The Event Tracker opt-out method works as follows:

  • Get the current Nielsen opt-out URL via userOptOutURLString()
  • Display a WebView element whose loadUrl is set to the value obtained from userOptOutURLString()
  • 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
  • OR - in case or Amazon FireTV or where a WebView cannot be used, create an opt-out button near your App's privacy policy or Nielsen verbiage
  • Update out-out status as shown below
  • Example (user opted out):
    trackEvent(
    {
        "optout":"nielsenappsdk://1"
    })  // User opt-out
    
  • Example (user opted in):
    trackEvent(
    {
        "optout":"nielsenappsdk://0"
    })  // User opt-in
    

Event Tracker Opt-out example code

Opt-out can be passed with other event data
trackEvent(
{
    "event": "playhead",
    "type": "content",
    "metadata":
    {
        "content":
        {
            "type":"content",
            "assetid":"assetid1",
            "isfullepisode":"y",
            "program":"MyProgram",
            "title":"S2,E3",
            "length":"3600",
            "airdate":"2016-02-20",
            "adloadtype":"1",
            ...
        }
    },
    "playheadPosition": "5",
    "optout":"nielsenappsdk://1"
})
Opt-out can be passed alone in separate trackEvent call
trackEvent(
{
    "optout":"nielsenappsdk://1"
})

Retrieve current Opt-Out preference

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 getOptOutStatus() property in the Nielsen Android SDK API,

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.


Webview Example

The below code is an example of displaying the Nielsen Privacy page to the user.

public class OptOutActivity extends AppCompatActivity implements IAppNotifier {

    WebView webView;
    AppSdk appSdk;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_optout);
        webView = (WebView) findViewById(R.id.webView);

        webView.getSettings().setJavaScriptEnabled(true);

        webView.setWebViewClient(new WebViewClient() {
            @SuppressWarnings("deprecation")
            @Override
            public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
                Toast.makeText(OptOutActivity.this, description, Toast.LENGTH_SHORT).show();
            }
            @TargetApi(android.os.Build.VERSION_CODES.M)
            @Override
            public void onReceivedError(WebView view, WebResourceRequest req, WebResourceError rerr) {
                // Redirect to deprecated method, so you can use it in all SDK versions
                onReceivedError(view, rerr.getErrorCode(), rerr.getDescription().toString(), req.getUrl().toString());
            }
        });

        NielsenInit nielsenInit = new NielsenInit();
        appSdk = nielsenInit.initAppSdk(getApplicationContext(), this);
        //Getting the optPut URL from eventTracker
        String url = appSdk.userOptOutURLString();
        webView.loadUrl(url);
    }


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. These 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 same session.

Audibility metrics will capture the volume level as well as mute/unmute state of the device while playback 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 has been added to the public SDK API (please refer to the trackViewability() public API reference for the details and usage examples):

void  trackViewability(JSONObject);


For Audibility measurement APPSDK uses android system API in order to get an volume level for the device:

AudioManager audio = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
float currentVolume = audio.getStreamVolume(AudioManager.STREAM_MUSIC);
float maxVolume = audio.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
int volumeLevel = (int) ((currentVolume/maxVolume) * 100);


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.

Android1.jpg

Android Studio Java Code Example

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

NielsenInit.java

// This is sample code of a very basic implementation of the Nielsen 'Simplified API'
// This code is for educational purposes only
//

import android.content.Context;

import com.nielsen.app.sdk.IAppNotifier;
import com.nielsen.app.sdk.NielsenEventTracker;

import org.json.JSONException;
import org.json.JSONObject;

public class NielsenInit {

    private NielsenEventTracker mEventTracker = null;

    public NielsenEventTracker initEventTracker(Context mContext, IAppNotifier appNotifier){

        try {

            //Initialising the NielsenEventTracker class by passing app information which returns the instance of NielsenEventTracker.

            JSONObject appInformation = new JSONObject()

                    .put("appid", "PDA7D5EE6-B1B8-4123-9277-2A788BC6XXXX")
                    .put("appversion", "1.0")
                    .put("appname", "Abdul's Android Test app")
                    .put("sfcode", "dcr")
                    .put("ccode", "123")
                    .put("dma","456")
                    .put("uoo","0")
                    .put("nol_devDebug", "INFO");
  
            mEventTracker = new NielsenEventTracker(mContext, appInformation, appNotifier);

        } catch (JSONException e) {
            e.printStackTrace();
        }

        return mEventTracker;
    }
}

SDKMethods.java

import org.json.JSONException;
import org.json.JSONObject;

/**
 * Created on 07/02/18.
 */

public class SDKMethods {

    public String url = "";
    JSONObject content = null;
  //Loading content Data
    public JSONObject loadContentData(){

        url = "http://www.nielseninternet.com/NielsenConsumer/prog_index.m3u8";

        JSONObject data = null;
        try {
            JSONObject content = new JSONObject()
                    .put( "assetName","ChromeCast1")
                    .put( "assetid","C77664")
                    .put( "length","3600")
                    .put( "program","MyProgram")
                    .put( "segB","CustomSegmentValueB")
                    .put( "segC","segmentC")
                    .put( "title","S2,E3")
                    .put( "type","content")
                    .put( "section","cloudApi_app")
                    .put( "airdate","20180120 10:00:00")
                    .put( "isfullepisode","y")
                    .put( "adloadtype","2")
                    .put( "channelName","My Channel 1")
                    .put( "pipMode","false");

            //Ad data,static data should be empty in content video dictionary
            JSONObject metaData = new JSONObject()
                    .put("content", content)
                    .put("ad", new JSONObject())
                    .put("static", new JSONObject());

            data = new JSONObject()
                    .put("metadata", metaData)
                    .put("event", "playhead")
                    .put("type", "content")
                    .put("playheadPosition", "0");



        } catch (JSONException e) {
                e.printStackTrace();
        }

        return data;

    }

MainActivity.java

package com.simplifiedapiapp.activities;

import android.app.ProgressDialog;
import android.media.MediaPlayer;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.Button;
import android.widget.SeekBar;

import com.simplifiedapiapp.utils.Constants;
import com.simplifiedapiapp.models.NielsenInit;
import com.simplifiedapiapp.R;
import com.simplifiedapiapp.models.SDKMethods;
import com.nielsen.app.sdk.IAppNotifier;
import com.nielsen.app.sdk.NielsenEventTracker;

import org.json.JSONException;
import org.json.JSONObject;

import java.io.IOException;

public class MainActivity extends AppCompatActivity implements IAppNotifier, SurfaceHolder.Callback, MediaPlayer.OnPreparedListener, View.OnClickListener, MediaPlayer.OnCompletionListener, MediaPlayer.OnErrorListener {

    public static final String TAG = MainActivity.class.getSimpleName();

    private SurfaceView mSurfaceView;
    private SeekBar seek;
    Button btnPlay;

    NielsenEventTracker eventTracker;
    private int videoType, totalVideos;
    private int totalVideosPlayed = 0;
    private boolean isVideoStarted = false, isPaused = false;
    JSONObject data = null;

    private MediaPlayer mMediaPlayer;
    private SurfaceHolder mSurfaceHolder;

    SDKMethods sdkMethods;

    private ProgressDialog dialog;
    private Handler playheadHandler;
    private Runnable playheadRunnable;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //In SDKMethods class we wrote methods which creates content,Ad objects
        sdkMethods = new SDKMethods();

        getIntents();
        initUI();

        //In NielsenInit class we are initialising the NielsenEventTracker.
        //Getting the instance of NielsenEventTracker
        NielsenInit nielsenInit = new NielsenInit();

        //3rd parameter "this" referes to IAppNotifier interface which is needed to initialise NielsenEventTracker
        eventTracker = nielsenInit.initEventTracker(getApplicationContext(), this);
    }

    @Override
    protected void onResume() {
        super.onResume();

        //loading static data
        JSONObject staticObj = sdkMethods.loadStaticData();

        //sending static data to SDK.
        eventTracker.trackEvent(staticObj);
    }

    private void initUI() {

        seek = (SeekBar) findViewById(R.id.seek);
        btnPlay = (Button) findViewById(R.id.btnPlay);
        btnPlay.setOnClickListener(this);
    }

    private void getIntents() {

        videoType = getIntent().getIntExtra(Constants.INTENT_VIDEO_TYPE, 0);
        totalVideos = getIntent().getIntExtra(Constants.INTENT_TOTAL_VIDEOS, 0);


        if (videoType == Constants.onlyContent) {

            //loading video content data
            data = sdkMethods.loadContentData();
        } else {

            //loading Ad data
            data = sdkMethods.loadPreRollAdData();
        }
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {

        Log.v(TAG, "surfaceCreated Called");

        if (isPaused) {
            //Once video is resumed after pause, setting surfaceholder to player.
            if (mMediaPlayer != null) {

                mSurfaceHolder = mSurfaceView.getHolder();
                mMediaPlayer.setDisplay(mSurfaceHolder);
            }

        } else {

            //This will execute for first time.
            setUpPlayer();
            mMediaPlayer.setOnCompletionListener(this);
            mMediaPlayer.setOnErrorListener(this);
        }
    }

    @Override
    public void onCompletion(MediaPlayer mediaPlayer) {
        try {

            playheadHandler.removeCallbacks(playheadRunnable);

            ///As 1 video completed playing, incrementing the variable value.
            totalVideosPlayed++;

            if (videoType == Constants.onlyContent || totalVideosPlayed == totalVideos) {

                //on content video complete, updating event as "complete" in object
                data.put("event", "complete");
            } else {
                //on Ad complete, updating event as "adStop" in object
                data.put("event", "adStop");
            }

            //sending the object to SDK.
            eventTracker.trackEvent(data);

            releaseMediaPlayer();

            checkVideosToBePlayed();

        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

    private void checkVideosToBePlayed(){

        //Checking if total videos played or not.
        if (totalVideosPlayed != totalVideos) {

            data = new JSONObject();

            //Checking if videoType is contentWithOneAd, then after completion of Ad, will play the content video.
            if (videoType == Constants.contentWithOneAd) {

                //loading video content data
                data = sdkMethods.loadContentData();

            } else if (videoType == Constants.contentWithTwoAds) {
                if (totalVideosPlayed == 1) {

                    //loading 2nd Ad data
                    data = sdkMethods.loadMidRollAdData();
                } else {

                    //loading video content data
                    data = sdkMethods.loadContentData();
                }
            }

            showProgressDialog();

            setUpPlayer();

            mMediaPlayer.setOnCompletionListener(this);
            mMediaPlayer.setOnErrorListener(this);

        }
    }

    @Override
    public boolean onError(MediaPlayer mediaPlayer, int i, int li) {
        Log.e(TAG, "Player error codes:" + i + ", " + li);
        return false;
    }

    //creating player
    private void setUpPlayer() {
        try {

            mMediaPlayer = new MediaPlayer();
            mMediaPlayer.setDisplay(mSurfaceHolder);
            mMediaPlayer.setDataSource(sdkMethods.url);
            mMediaPlayer.setOnPreparedListener(MainActivity.this);
            mMediaPlayer.prepareAsync();

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        Log.v(TAG, "surfaceChanged Called");
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        Log.v(TAG, "surfaceDestroyed Called");
    }

    @Override
    public void onPrepared(MediaPlayer mp) {

        if (dialog.isShowing()) {
            dialog.dismiss();
        }

        seek.setMax(convertTotime(mMediaPlayer.getDuration()));
        isVideoStarted = true;

        updateSeekbarAndPlayhead();
        mMediaPlayer.start();

    }

    @Override
    protected void onPause() {
        super.onPause();

        setPauseAction();
    }

    private void setPauseAction() {
        try {
            if (mMediaPlayer != null) {

                isVideoStarted = false;
                isPaused = true;
                mMediaPlayer.pause();


                //on video pause, updating event as pause in object
                data.put("event", "pause");

                //sending the object to SDK with "pause" event.
                eventTracker.trackEvent(data);

                btnPlay.setText(getString(R.string.play));

            }

        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        releaseMediaPlayer();
    }

    private void releaseMediaPlayer() {
        if (mMediaPlayer != null) {
            mMediaPlayer.release();
            mMediaPlayer = null;
        }
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.btnPlay:
                try {
                    //If video is not yet played, then it will play else it will pause the video
                    if (!isVideoStarted) {

                        btnPlay.setText(getString(R.string.pause));

                        if (isPaused) {

                            isVideoStarted = true;
                            isPaused = false;

                            //Once the video is resumed after pause, setting event as "playhead".
                            data.put("event", "playhead");
                            mMediaPlayer.start();

                        } else {

                            mSurfaceView = (SurfaceView) findViewById(R.id.surface_view);
                            mSurfaceView.setVisibility(View.VISIBLE);

                            showProgressDialog();

                            mSurfaceHolder = mSurfaceView.getHolder();
                            mSurfaceHolder.addCallback(MainActivity.this);

                        }

                    } else {
                        setPauseAction();
                    }

                } catch (JSONException e) {
                    e.printStackTrace();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                break;
        }

    }

    //Updates seekbar
    private void updateSeekbarAndPlayhead() {
        try {
            //to update playhead, setting the event as "playhead".
            data.put("event", "playhead");
            playheadHandler = new Handler();


            //Make sure you update Seekbar on UI thread
            MainActivity.this.runOnUiThread(playheadRunnable = new Runnable() {

                @Override
                public void run() {
                    if (mMediaPlayer != null) {
                        int mCurrentPosition = mMediaPlayer.getCurrentPosition() / 1000;
                        seek.setProgress(mCurrentPosition);
                        try {
                            if (!isPaused && mMediaPlayer.isPlaying()) {

                                //updating playHead position in Object each second.
                                data.put("playheadPosition", String.valueOf(mCurrentPosition));

                                //Sending data object to SDK with updated playHead position.
                                eventTracker.trackEvent(data);
                            }
                        } catch (JSONException e) {
                            e.printStackTrace();
                        }

                    }
                    playheadHandler.postDelayed(this, 1000);
                }
            });

        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

    private int convertTotime(long milliSec) {

        int minutes = (int) (milliSec / 1000);
        return minutes;
    }

    private void showProgressDialog() {

        runOnUiThread(new Runnable() {
            public void run() {
                dialog = new ProgressDialog(MainActivity.this);
                dialog.setCancelable(false);
                dialog.setMessage(getString(R.string.loading));
                dialog.show();
            }
        });
    }


    @Override
    public void onAppSdkEvent(long l, int i, String s) {

        //IAppNotifier method
    }
}