https://engineeringportal.nielsen.com//w/api.php?action=feedcontributions&user=AlexGutierrez&feedformat=atomEngineering Client Portal - User contributions [en]2024-03-28T19:21:30ZUser contributionsMediaWiki 1.32.2https://engineeringportal.nielsen.com//w/index.php?title=end&diff=3900end2019-07-09T15:56:06Z<p>AlexGutierrez: </p>
<hr />
<div>{{Breadcrumb|}} {{Breadcrumb|Digital}} {{Breadcrumb|iOS SDK API Reference}} {{CurrentBreadcrumb}}<br />
[[Category:Digital]]<br />
[[Category:iOS SDK API Reference]]<br />
<br />
Stops measurement progress. Call end in following situations.<br />
* When the playback is completed (user watches until end of content).<br />
* When content stop is initiated and content cannot be resumed from the same position (it can only be restarted from the beginning of stream).<br />
<br />
== Syntax ==<br />
<syntaxhighlight lang="objective-c"><br />
- (void) end;<br />
</syntaxhighlight><br />
<br />
== Input Parameters ==<br />
{| class="wikitable"<br />
|-<br />
! Parameter !! Description<br />
|-<br />
| None || <br />
|}<br />
<br />
== Output Parameters ==<br />
{| class="wikitable"<br />
|-<br />
! Output Parameters (Return value) !! Description<br />
|-<br />
| Void || <br />
|}<br />
<br />
== Notes ==<br />
It is very important that the SDK calls <code>end</code> whenever necessary.</div>AlexGutierrezhttps://engineeringportal.nielsen.com//w/index.php?title=DTVR_Browser_SDK&diff=3887DTVR Browser SDK2019-06-26T20:04:52Z<p>AlexGutierrez: </p>
<hr />
<div>{{Breadcrumb|}} {{Breadcrumb|Digital}} {{Breadcrumb|DCR & DTVR}} {{CurrentBreadcrumb}}<br />
[[Category:Digital]]<br />
<br />
== Prerequisites ==<br />
To start using the App SDK, the following items are required:<br />
{| class="wikitable"<br />
|-<br />
! style="width: 30px;" |<br />
! style="width: 15%;" | Item<br />
! Description<br />
! Source<br />
|-<br />
|| ☑ || '''App ID (appid)''' || Unique ID assigned to the player/site and configured by product. || Contact Nielsen<br />
|}<br />
<br />
== Configure SDK ==<br />
There are two steps required for configuring your SDK: 1. Add Static Queue Snippet and 2. Create SDK Instance.<br />
<br />
== Add Static Queue Snippet ==<br />
Add the following script tag to your website:<br />
<br />
<syntaxhighlight lang="javascript"><br />
<script><br />
! function(t, n) {<br />
t[n] = t[n] || {<br />
nlsQ: function(e, o, c, r, s, i) {<br />
return s = t.document,<br />
r = s.createElement("script"),<br />
r.async = 1,<br />
r.src = ("http:" === t.location.protocol ? "http:" : "https:") + "//cdn-gl.imrworldwide.com/conf/" + e + ".js#name=" + o + "&ns=" + n,<br />
i = s.getElementsByTagName("script")[0],<br />
i.parentNode.insertBefore(r, i),<br />
t[n][o] = t[n][o] || {<br />
g: c || {},<br />
ggPM: function(e, c, r, s, i) {<br />
(t[n][o].q = t[n][o].q || []).push([e, c, r, s, i])<br />
}<br />
}, t[n][o]<br />
}<br />
}<br />
}<br />
(window, "NOLBUNDLE");<br />
</script><br />
</syntaxhighlight><br />
<br />
The static queue snippet allows the SDK APIs to be called while the actual SDK and configuration file are still being downloaded. Since the queue is able to capture all API calls before the download completes, there is no wait time. Once the SDK is available, the API calls will transition from directing to the queue to the SDK seamlessly.<br />
<br />
== Create SDK Instance ==<br />
To initialize the SDK, you will need to create an SDK instance by making the initialization call:<br />
<br />
<syntaxhighlight lang="javascript"><br />
NOLBUNDLE.nlsQ("<apid>", "<instanceName>",{nol_sdkDebug: "debug"})<br />
</syntaxhighlight><br />
<br /><br />
When creating your instance, you will need to pass three parameter values. The available parameters are listed in the table below:<br />
<br />
{| class="wikitable"<br />
|-<br />
! Parameters !! Description !! Value !! Required? (Y/N)<br />
|-<br />
| apid || UniqueID assigned to player/site. <br />
|| "XXXXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXX" || Yes<br />
|-<br />
| instanceName || User-defined string value for describing the player/site. || Client specified || Yes<br />
|-<br />
| sfcode || Location of collection environment. All traffic should be directed to "dcr". || dcr || Yes<br />
|-<br />
| nol_sdkDebug:"debug" || Enables Debug Mode which allows output to be viewed in console. || "{nol_sdkDebug: "debug"}" || No<br />
|}<br />
<br />
== Example SDK Initialization ==<br />
<syntaxhighlight lang="javascript"><br />
var nSdkInstance = NOLBUNDLE.nlsQ("XXXXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXX", "nlsnInstance", {nol_sdkDebug: "debug"});<br />
</syntaxhighlight><br />
<br />
When the initialization call is made, a unique static configuration file, <apid>.js, will be downloaded based on your apid and cached on the user's browser.<br />
<br />
Once the configuration is downloaded, the SDK itself will be downloaded and initialized. All SDK modules are included in one file: "nlsSDK600.bundle.min.js".<br />
<br />
=== Example SDK Initialization ===<br />
Your configuration should include the Static Queue Snippet and an SDK Instance for your unique App ID as shown in the example:<br />
<br />
<syntaxhighlight lang="javascript"><br />
<br />
//Add Static Queue Snippet<br />
<script><br />
! function(t, n) {<br />
t[n] = t[n] || {<br />
nlsQ: function(e, o, c, r, s, i) {<br />
return s = t.document,<br />
r = s.createElement("script"),<br />
r.async = 1,<br />
r.src = ("http:" === t.location.protocol ? "http:" : "https:") + "//cdn-gl.imrworldwide.com/conf/" + e + ".js#name=" + o + "&ns=" + n,<br />
i = s.getElementsByTagName("script")[0],<br />
i.parentNode.insertBefore(r, i),<br />
t[n][o] = t[n][o] || {<br />
g: c || {},<br />
ggPM: function(e, c, r, s, i) {<br />
(t[n][o].q = t[n][o].q || []).push([e, c, r, s, i])<br />
}<br />
}, t[n][o]<br />
}<br />
}<br />
}<br />
(window, "NOLBUNDLE");<br />
<br />
//Create SDK Instance<br />
var nSdkInstance = NOLBUNDLE.nlsQ("XXXXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXX", "nlsnInstance", {nol_sdkDebug: "debug"});<br />
</script><br />
</syntaxhighlight><br />
<br />
=== Create Metadata Objects ===<br />
<br /><br />
There are two types of asset metadata:<br />
<br /><br />
*content: identify video<br />
*ad: identify each ad<br />
<br /><br />
The metadata received for each asset is used for classification and reporting.<br />
<br /><br />
Metadata can be passed through key-values using the Nielsen reserved keys. User will need to set up content and ad objects with the required Nielsen keys as shown in the sample code below.<br />
<br /><br />
<br />
== Content Metadata ==<br />
<br /><br />
Content metadata should remain constant throughout the completion of an episode or live stream.<br />
<br /><br />
{| class="wikitable"<br />
|-<br />
! Key !! Description !! Values !! Required<br />
|-<br />
| type || type of asset || "content" || ✓<br />
|-<br />
| channelName || Any string representing the channel/stream || custom || <br />
|-<br />
| adModel || linear vs dynamic ad model || * 1) - Linear – matches TV ad load * 2) Dynamic – Dynamic Ad Insertion (DAI) || ✓<br />
|-<br />
|}<br />
<br/><br />
'''Example Content Object'''<br />
<syntaxhighlight lang="javascript"> var contentMetadataObject =<br />
{ <br />
type: 'content',<br />
channelName: 'Live Player',<br />
adModel: '1'<br />
};</syntaxhighlight><br />
<br />
=== Ad Metadata Object ===<br />
<br/><br />
The ad metadata should be passed for each individual ad, if ads are available during or before the stream begins.<br />
<br/><br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| type || type of ad || 'preroll', 'midroll', or 'postroll' || ✓<br />
|-<br />
| assetid || unique ID assigned to ad || custom (no [[Special Characters]]) || ✓<br />
|}<br />
<br/><br />
'''Example Ad Object'''<br />
<br/><br />
<syntaxhighlight lang="javascript"> var adMetadataObject = <br />
{ <br />
assetid: 'AD-1',<br />
type: 'preroll'<br />
};</syntaxhighlight><br />
<br />
=== SDK Events ===<br />
<br/><br />
{| class="wikitable"<br />
|-<br />
! Event !! Parameter !! Description<br />
|-<br />
| 'loadMetadata' || content/ad metadata object || Needs to be called at the beginning of each asset to pass type, channelName, and adModel.<br />
|-<br />
| 'sendID3' || Used to send the ID3 tag payload retrieved from the stream || Needs to be called at the beginning of playback<br />
||<br />
|-<br />
| 'end' || playhead position in seconds or Unix time || Call when the current video asset completes playback or when a stream is interrupted. <br/><br />
Example: At the end of the content stream, if the user switches to another piece of content, when the browser is refreshed or closed.<br />
|}<br />
<br />
== Configure and fire API calls ==<br />
<br /><br />
The syntax for firing events is:<br />
<syntaxhighlight lang="javascript"> nSdkInstance.ggPM("event", parameter object);</syntaxhighlight><br />
<br /><br />
Event is passed in parameter 1 and the argument is passed in parameter 2.<br />
<br />
=== Configure API calls - loadMetadata ===<br />
<br /><br />
Use [[loadMetadata (Browser)]] to pass the metadata object. The data must be passed as a JSON string.<br />
<br /><br />
<syntaxhighlight lang="javascript">nSdkInstance.ggPM("loadMetadata", metadataObject);</syntaxhighlight><br />
<br />
=== Configure API calls - sendID3 ===<br />
<br /><br />
Use [[sendID3 (Browser)]] to send ID3 payload of the HLS content being played. This will allow the ID3 payload to be sent every time an ID3 packet is received (approximately, once in every 10 seconds).<br />
<br /><br />
<syntaxhighlight lang="javascript">nSdkInstance.ggPM("sendID3", ID3_Object);</syntaxhighlight><br />
<br /><br />
==== Sample ID3 tags ====<br />
<br /><br />
<syntaxhighlight><br />
www.nielsen.com/X100zdCIGeIlgZnkYj6UvQ==/X100zdCIGeIlgZnkYj6UvQ==/AAAB2Jz2_k74GXSzx4npHuI_<wbr />JwJd3QSUpW30rDkGTcbHEzIMWleCzM-uvNOP9fzJcQMWQLJqzXMCAxParOb5sGijSV9dNM3QiBniJYGZ5GI-lL1fXTTN0IgZ4iWBmeRiPpS9AAAAAAAAAAAAAAAAAAAAAFJWFM5SVhTONNU=/00000/00000/00</syntaxhighlight><br />
<br /><br />
<syntaxhighlight>www.nielsen.com/X100zdCIGeIlgZnkYj6UvQ==/R8WHe7pEBeqBhu8jTeXydg==/AAICoyitYqlxT7n6aZ0oMCGhe<wbr />Fi4CXFp46AMUPZz1lMr_M9tr3_cjee1SHqxrOiVerMDLeyn9xzocZSKwi746Re8vNOtpNCAZjYABs_J0R25IHpvOc1HS8<wbr />QHGgD5TgOJeS6gX100zdCIGeIlgZnkYj6UvVJWFNhSVhTiPE0=/00000/46016/00</syntaxhighlight><br />
<br /><br />
<code>ID3_Object</code> is the container to pass the retrieved ID3 tag from the streaming. The player should look for 'PRIV' ID3 tags and send 'owner' field (which typically starts from "www.nielsen.com") through this API. Refer to [[Browser SDK API Reference#Retrieving ID3 Tags|Browser SDK API Reference - Retrieving ID3 Tags]] for more information.<br />
<br /><br />
<br /><br />
Refer to [[Browser SDK API Reference#Retrieving ID3 Tags|Browser SDK API Reference - Retrieving ID3 Tags]] section to know more details.<br />
<br />
=== Configure API calls - end ===<br />
<br /><br />
Call [[end (Browser)]] only at the end of playback, or if the stream is interrupted.<br />
<syntaxhighlight lang="javascript">nSdkInstance.ggPM("end", playhead);</syntaxhighlight><br />
<br />
== SDK DTVR Event Sequence ==<br />
<br/><br />
The sample event lifecycle can be used as a reference for identifying the order for calling events.<br />
<br/><br />
<syntaxhighlight lang="javascript"> // START OF STREAM<br />
nSdkInstance.ggPM('loadMetadata', contentMetadataObject); <br />
nSdkInstance.ggPM('sendID3', ID3_Payload);<br />
<br />
//Upon completion of a stream, or upon an interruption scenario call 'end' and pass the playhead position as an integer or Unix timestamp (seconds since Jan-1-1970 UTC)<br />
<br />
nSdkInstance.ggPM('end', playhead);<br />
<br />
</syntaxhighlight><br />
<br />
{{Template:Browser_Privacy_and_Opt-Out}}<br />
<br />
== Going Live ==<br />
After the integration has been certified, users will need to make a couple of updates to the initialization call to ensure that player will be measured properly.<br />
Disable debug logging by deleting {nol_sdkDebug: 'DEBUG'} from initialization call.<br />
Example Production Initialization Call<br />
<br />
<syntaxhighlight lang="javascript"><br />
var nSdkInstance = NOLBUNDLE.nlsQ("PXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX", "nlsnInstance");<br />
</syntaxhighlight></div>AlexGutierrezhttps://engineeringportal.nielsen.com//w/index.php?title=DTVR_Browser_SDK&diff=3886DTVR Browser SDK2019-06-26T20:02:49Z<p>AlexGutierrez: Changed playhead position description per information from Amy</p>
<hr />
<div>{{Breadcrumb|}} {{Breadcrumb|Digital}} {{Breadcrumb|DCR & DTVR}} {{CurrentBreadcrumb}}<br />
[[Category:Digital]]<br />
<br />
== Prerequisites ==<br />
To start using the App SDK, the following items are required:<br />
{| class="wikitable"<br />
|-<br />
! style="width: 30px;" |<br />
! style="width: 15%;" | Item<br />
! Description<br />
! Source<br />
|-<br />
|| ☑ || '''App ID (appid)''' || Unique ID assigned to the player/site and configured by product. || Contact Nielsen<br />
|}<br />
<br />
== Configure SDK ==<br />
There are two steps required for configuring your SDK: 1. Add Static Queue Snippet and 2. Create SDK Instance.<br />
<br />
== Add Static Queue Snippet ==<br />
Add the following script tag to your website:<br />
<br />
<syntaxhighlight lang="javascript"><br />
<script><br />
! function(t, n) {<br />
t[n] = t[n] || {<br />
nlsQ: function(e, o, c, r, s, i) {<br />
return s = t.document,<br />
r = s.createElement("script"),<br />
r.async = 1,<br />
r.src = ("http:" === t.location.protocol ? "http:" : "https:") + "//cdn-gl.imrworldwide.com/conf/" + e + ".js#name=" + o + "&ns=" + n,<br />
i = s.getElementsByTagName("script")[0],<br />
i.parentNode.insertBefore(r, i),<br />
t[n][o] = t[n][o] || {<br />
g: c || {},<br />
ggPM: function(e, c, r, s, i) {<br />
(t[n][o].q = t[n][o].q || []).push([e, c, r, s, i])<br />
}<br />
}, t[n][o]<br />
}<br />
}<br />
}<br />
(window, "NOLBUNDLE");<br />
</script><br />
</syntaxhighlight><br />
<br />
The static queue snippet allows the SDK APIs to be called while the actual SDK and configuration file are still being downloaded. Since the queue is able to capture all API calls before the download completes, there is no wait time. Once the SDK is available, the API calls will transition from directing to the queue to the SDK seamlessly.<br />
<br />
== Create SDK Instance ==<br />
To initialize the SDK, you will need to create an SDK instance by making the initialization call:<br />
<br />
<syntaxhighlight lang="javascript"><br />
NOLBUNDLE.nlsQ("<apid>", "<instanceName>",{nol_sdkDebug: "debug"})<br />
</syntaxhighlight><br />
<br /><br />
When creating your instance, you will need to pass three parameter values. The available parameters are listed in the table below:<br />
<br />
{| class="wikitable"<br />
|-<br />
! Parameters !! Description !! Value !! Required? (Y/N)<br />
|-<br />
| apid || UniqueID assigned to player/site. <br />
|| "XXXXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXX" || Yes<br />
|-<br />
| instanceName || User-defined string value for describing the player/site. || Client specified || Yes<br />
|-<br />
| sfcode || Location of collection environment. All traffic should be directed to "dcr". || dcr || Yes<br />
|-<br />
| nol_sdkDebug:"debug" || Enables Debug Mode which allows output to be viewed in console. || "{nol_sdkDebug: "debug"}" || No<br />
|}<br />
<br />
== Example SDK Initialization ==<br />
<syntaxhighlight lang="javascript"><br />
var nSdkInstance = NOLBUNDLE.nlsQ("XXXXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXX", "nlsnInstance", {nol_sdkDebug: "debug"});<br />
</syntaxhighlight><br />
<br />
When the initialization call is made, a unique static configuration file, <apid>.js, will be downloaded based on your apid and cached on the user's browser.<br />
<br />
Once the configuration is downloaded, the SDK itself will be downloaded and initialized. All SDK modules are included in one file: "nlsSDK600.bundle.min.js".<br />
<br />
=== Example SDK Initialization ===<br />
Your configuration should include the Static Queue Snippet and an SDK Instance for your unique App ID as shown in the example:<br />
<br />
<syntaxhighlight lang="javascript"><br />
<br />
//Add Static Queue Snippet<br />
<script><br />
! function(t, n) {<br />
t[n] = t[n] || {<br />
nlsQ: function(e, o, c, r, s, i) {<br />
return s = t.document,<br />
r = s.createElement("script"),<br />
r.async = 1,<br />
r.src = ("http:" === t.location.protocol ? "http:" : "https:") + "//cdn-gl.imrworldwide.com/conf/" + e + ".js#name=" + o + "&ns=" + n,<br />
i = s.getElementsByTagName("script")[0],<br />
i.parentNode.insertBefore(r, i),<br />
t[n][o] = t[n][o] || {<br />
g: c || {},<br />
ggPM: function(e, c, r, s, i) {<br />
(t[n][o].q = t[n][o].q || []).push([e, c, r, s, i])<br />
}<br />
}, t[n][o]<br />
}<br />
}<br />
}<br />
(window, "NOLBUNDLE");<br />
<br />
//Create SDK Instance<br />
var nSdkInstance = NOLBUNDLE.nlsQ("XXXXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXX", "nlsnInstance", {nol_sdkDebug: "debug"});<br />
</script><br />
</syntaxhighlight><br />
<br />
=== Create Metadata Objects ===<br />
<br /><br />
There are two types of asset metadata:<br />
<br /><br />
*content: identify video<br />
*ad: identify each ad<br />
<br /><br />
The metadata received for each asset is used for classification and reporting.<br />
<br /><br />
Metadata can be passed through key-values using the Nielsen reserved keys. User will need to set up content and ad objects with the required Nielsen keys as shown in the sample code below.<br />
<br /><br />
<br />
== Content Metadata ==<br />
<br /><br />
Content metadata should remain constant throughout the completion of an episode or live stream.<br />
<br /><br />
{| class="wikitable"<br />
|-<br />
! Key !! Description !! Values !! Required<br />
|-<br />
| type || type of asset || "content" || ✓<br />
|-<br />
| channelName || Any string representing the channel/stream || custom || <br />
|-<br />
| adModel || linear vs dynamic ad model || * 1) - Linear – matches TV ad load * 2) Dynamic – Dynamic Ad Insertion (DAI) || ✓<br />
|-<br />
|}<br />
<br/><br />
'''Example Content Object'''<br />
<syntaxhighlight lang="javascript"> var contentMetadataObject =<br />
{ <br />
type: 'content',<br />
channelName: 'Live Player',<br />
adModel: '1'<br />
};</syntaxhighlight><br />
<br />
=== Ad Metadata Object ===<br />
<br/><br />
The ad metadata should be passed for each individual ad, if ads are available during or before the stream begins.<br />
<br/><br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| type || type of ad || 'preroll', 'midroll', or 'postroll' || ✓<br />
|-<br />
| assetid || unique ID assigned to ad || custom (no [[Special Characters]]) || ✓<br />
|}<br />
<br/><br />
'''Example Ad Object'''<br />
<br/><br />
<syntaxhighlight lang="javascript"> var adMetadataObject = <br />
{ <br />
assetid: 'AD-1',<br />
type: 'preroll'<br />
};</syntaxhighlight><br />
<br />
=== SDK Events ===<br />
<br/><br />
{| class="wikitable"<br />
|-<br />
! Event !! Parameter !! Description<br />
|-<br />
| 'loadMetadata' || content/ad metadata object || Needs to be called at the beginning of each asset to pass type, channelName, and adModel.<br />
|-<br />
| 'sendID3' || Used to send the ID3 tag payload retrieved from the stream || Needs to be called at the beginning of playback<br />
||<br />
|-<br />
| 'end' || playhead position in seconds || Call when the current video asset completes playback or when a stream is interrupted. <br/><br />
Example: At the end of the content stream, if the user switches to another piece of content, when the browser is refreshed or closed.<br />
|}<br />
<br />
== Configure and fire API calls ==<br />
<br /><br />
The syntax for firing events is:<br />
<syntaxhighlight lang="javascript"> nSdkInstance.ggPM("event", parameter object);</syntaxhighlight><br />
<br /><br />
Event is passed in parameter 1 and the argument is passed in parameter 2.<br />
<br />
=== Configure API calls - loadMetadata ===<br />
<br /><br />
Use [[loadMetadata (Browser)]] to pass the metadata object. The data must be passed as a JSON string.<br />
<br /><br />
<syntaxhighlight lang="javascript">nSdkInstance.ggPM("loadMetadata", metadataObject);</syntaxhighlight><br />
<br />
=== Configure API calls - sendID3 ===<br />
<br /><br />
Use [[sendID3 (Browser)]] to send ID3 payload of the HLS content being played. This will allow the ID3 payload to be sent every time an ID3 packet is received (approximately, once in every 10 seconds).<br />
<br /><br />
<syntaxhighlight lang="javascript">nSdkInstance.ggPM("sendID3", ID3_Object);</syntaxhighlight><br />
<br /><br />
==== Sample ID3 tags ====<br />
<br /><br />
<syntaxhighlight><br />
www.nielsen.com/X100zdCIGeIlgZnkYj6UvQ==/X100zdCIGeIlgZnkYj6UvQ==/AAAB2Jz2_k74GXSzx4npHuI_<wbr />JwJd3QSUpW30rDkGTcbHEzIMWleCzM-uvNOP9fzJcQMWQLJqzXMCAxParOb5sGijSV9dNM3QiBniJYGZ5GI-lL1fXTTN0IgZ4iWBmeRiPpS9AAAAAAAAAAAAAAAAAAAAAFJWFM5SVhTONNU=/00000/00000/00</syntaxhighlight><br />
<br /><br />
<syntaxhighlight>www.nielsen.com/X100zdCIGeIlgZnkYj6UvQ==/R8WHe7pEBeqBhu8jTeXydg==/AAICoyitYqlxT7n6aZ0oMCGhe<wbr />Fi4CXFp46AMUPZz1lMr_M9tr3_cjee1SHqxrOiVerMDLeyn9xzocZSKwi746Re8vNOtpNCAZjYABs_J0R25IHpvOc1HS8<wbr />QHGgD5TgOJeS6gX100zdCIGeIlgZnkYj6UvVJWFNhSVhTiPE0=/00000/46016/00</syntaxhighlight><br />
<br /><br />
<code>ID3_Object</code> is the container to pass the retrieved ID3 tag from the streaming. The player should look for 'PRIV' ID3 tags and send 'owner' field (which typically starts from "www.nielsen.com") through this API. Refer to [[Browser SDK API Reference#Retrieving ID3 Tags|Browser SDK API Reference - Retrieving ID3 Tags]] for more information.<br />
<br /><br />
<br /><br />
Refer to [[Browser SDK API Reference#Retrieving ID3 Tags|Browser SDK API Reference - Retrieving ID3 Tags]] section to know more details.<br />
<br />
=== Configure API calls - end ===<br />
<br /><br />
Call [[end (Browser)]] only at the end of playback, or if the stream is interrupted.<br />
<syntaxhighlight lang="javascript">nSdkInstance.ggPM("end", playhead);</syntaxhighlight><br />
<br />
== SDK DTVR Event Sequence ==<br />
<br/><br />
The sample event lifecycle can be used as a reference for identifying the order for calling events.<br />
<br/><br />
<syntaxhighlight lang="javascript"> // START OF STREAM<br />
nSdkInstance.ggPM('loadMetadata', contentMetadataObject); <br />
nSdkInstance.ggPM('sendID3', ID3_Payload);<br />
<br />
//Upon completion of a stream, or upon an interruption scenario call 'end' and pass the playhead position as an integer or Unix timestamp (seconds since Jan-1-1970 UTC)<br />
<br />
nSdkInstance.ggPM('end', playhead);<br />
<br />
</syntaxhighlight><br />
<br />
{{Template:Browser_Privacy_and_Opt-Out}}<br />
<br />
== Going Live ==<br />
After the integration has been certified, users will need to make a couple of updates to the initialization call to ensure that player will be measured properly.<br />
Disable debug logging by deleting {nol_sdkDebug: 'DEBUG'} from initialization call.<br />
Example Production Initialization Call<br />
<br />
<syntaxhighlight lang="javascript"><br />
var nSdkInstance = NOLBUNDLE.nlsQ("PXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX", "nlsnInstance");<br />
</syntaxhighlight></div>AlexGutierrezhttps://engineeringportal.nielsen.com//w/index.php?title=DCR_Video_%26_Static_Roku_Cloud_API&diff=3633DCR Video & Static Roku Cloud API2019-05-03T16:53:59Z<p>AlexGutierrez: </p>
<hr />
<div>{{Breadcrumb|}} {{Breadcrumb|Digital}} {{Breadcrumb|DCR & DTVR}} {{CurrentBreadcrumb}}<br />
[[Category:Digital]]<br />
<br />
This guide shows you how to integrate the Nielsen Cloud API to enable Digital Content Ratings (DCR) measurement on your Roku Apps.<br />
*For other OTT Apps, please see [[DCR Video & Static Cloud API]]<br />
*For Mobile Apps, please see [[DCR Video & Static Mobile Cloud API]]<br />
<br />
==Prerequisites==<br />
To get started, you will need a Nielsen App ID. The App ID is a unique ID assigned to your app. This will be provided to you upon starting the integration.<br />
<br />
<syntaxhighlight lang="javascript">XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX</syntaxhighlight><br />
<br />
==Integration==<br />
We will cover the steps for constructing the Cloud API Calls.<br />
<br />
===Request Overview===<br />
<br />
====URL Structure====<br />
<br />
The Cloud API Calls are HTTP GET Requests with the URL structure:<br />
<br />
<syntaxhighlight lang="javascript">[endpoint]/[appid]/[sessionID]/a?b=[payload]</syntaxhighlight><br />
<br />
The URL includes the following components:<br />
<br />
*<code>[endpoint]</code>: location of data collection environment<br />
*<code>[appid]</code>: provided App ID<br />
*<code>[sessionID]</code>: unique value for each user session<br />
*<code>[payload]</code>: metadata and events<br />
<br />
====Endpoint====<br />
<br />
There are endpoints for testing and production:<br />
<br />
*Testing: <code>https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/</code><br />
*Production: <code>https://cloudapi.imrworldwide.com/nmapi/v2/</code><br />
<br />
During testing, all calls should be pointed to the testing endpoint. Once your application has been certified, the URL should be updated to point to the Cloud API production endpoint.<br />
<br />
====URL Example====<br />
As you move through the integration steps, you can reference the below URL structure with the expanded payload:<br />
<br />
<syntaxhighlight lang="javascript"><br />
https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=<br />
{<br />
"devInfo": [deviceInfo],<br />
"metadata": {<br />
"static": [static_metadata],<br />
"content": [content_metadata],<br />
"ad": [ad metadata]<br />
},<br />
"event": [event],<br />
"position": [playhead_position],<br />
"type": [asset type],<br />
"utc": [UTC]<br />
}<br />
</syntaxhighlight><br />
<br />
===Create Session ID===<br />
A unique Session ID must be created upon app launch and provided in the URL. This will allow measurement to occur for the entire duration that a user is within the app.<br />
<br />
The session ID must be passed with every request and must remain consistent throughout each individual session.<br />
<br />
A Session ID needs to be completely unique so it is recommended to use a version 4 UUID or another method of your choosing to guarantee there are no repeats.<br />
<br />
Upon exiting the app, the session will need to be terminated using the delete event. Sessions will automatically expire after 30 minutes of cloud inactivity.<br />
<br />
===Define URL Structure===<br />
Define the URL structure using your provided <code>[appid]</code> and a unique <code>[sessionID]</code>.<br />
<br />
<syntaxhighlight lang="javascript">https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=[payload]</syntaxhighlight><br />
<br />
===Configure Payload===<br />
<br />
All Cloud API requests must contain the following payload data:<br />
<br />
*''devInfo'': device and app info<br />
*''metadata'': asset metadata<br />
*''event metadata'': type of event<br />
<br />
The payload can be passed through key-values using the Nielsen reserved keys. The specific keys and descriptions are highlighted in the tables included in this section.<br />
<br />
'''Payload Example'''<br />
<br />
The example below should be referenced when following the steps for configuring the request payload.<br />
<br />
<syntaxhighlight lang="javascript"><br />
// 3.1 Configure Payload: devInfo <br />
payload = {<br />
"devInfo": {<br />
"apn": "AppName",<br />
"encdata": "encdata=v1.key.RokuAES256CBC00.iv3x2EV0BpHH9AbZK%2FnBWWRhZbj7pD%3D...",<br />
"apv": "1.0",<br />
"uoo": "false"<br />
},<br />
<br />
// 3.2 Configure Payload: metadata<br />
"metadata": {<br />
"static": {}, // object for measuring static content<br />
"content": { // object for measuring video content<br />
"type": "content", // "content" for video<br />
"assetid": "VIDEO-ID123", // unique ID for video<br />
"isfullepisode": "y", // full episode flag<br />
"program": "Program Name", // program name<br />
"title": "Episode Title S3 - EP1", // episode name<br />
"length": "1800", // content duration in seconds<br />
"segB": "Custom Segment B", // custom segment<br />
"segC": "Custom Segment C", // custom segment<br />
"crossId1": "Standard Episode ID", // episode ID<br />
"crossId2": "Content Originator ID", // content orginator (required for distributors)<br />
"airdate": "20161013 20:00:00", // airdate<br />
"adloadtype": "2", //ad load flag<br />
"hasAds": "1", // content contains ads = 1 / no ads = 0<br />
"progen": "CV" // program genre abbreviation<br />
},<br />
"ad": {<br />
"type": "preroll", // type of ad<br />
"assetid": "AD-ID123" // unique ID for ad<br />
}<br />
},<br />
<br />
// 3.3 Configure Payload: events<br />
"event": "playhead", //event name<br />
"position": "300", // position in seconds<br />
"type": "content", //"content" or "ad"<br />
"utc": "1456448742000" //unix timestamp in milliseconds <br />
}<br />
</syntaxhighlight><br />
<br />
=====Configure Payload: devInfo=====<br />
An object <code>"devInfo"</code> will need to be created to capture App and Device information.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| encdata || Nielsen - Roku ID for Advertisers || N-RIDA Payload || Yes<br />
|-<br />
| apn || app name || custom || Yes<br />
|-<br />
| apv || app build version || custom || Yes<br />
|-<br />
| uoo || device opt-out status || <code>"true"</code> or <code>"false"</code> || Yes<br />
|-<br />
| Example || Example || Example || Yes<br />
|}<br />
<br />
'''Example devInfo Object'''<br />
<syntaxhighlight lang="javascript"><br />
// create devInfo object<br />
"devInfo": {<br />
"encdata": "encdata=v1.key.RokuAES256CB...",<br />
"apn": "AppName",<br />
"apv": "1.0",<br />
"uoo": "false"<br />
},<br />
</syntaxhighlight><br />
<br />
===== Encdata (N-RIDA): Nielsen - Roku Identifier for Advertisers =====<br />
The N-RIDA (Nielsen-Roku ID for Advertisers) is a proprietary advertising identifier that ties back to an individual Roku device, and allows Nielsen to attribute audience metrics. The N-RIDA is provided by Roku, and is pulled through the Roku <code>[https://sdkdocs.roku.com/display/sdkdoc/Integrating+the+Roku+Advertising+Framework#IntegratingtheRokuAdvertisingFramework-getNielsenContentData()asString AdIface.GetNielsenContentData()]</code> API.<br />
<br />
The N-RIDA payload should be requested once per user session and populated in the <code>"encdata"</code> Nielsen key within the <code>"devInfo"</code> payload. Each Cloud API request throughout a user session should include the same N-RIDA payload. The N-RIDA should be established when a user launches the app, and it should be maintained until the user exits. A new N-RIDA payload should be requested when the user re-enters the app.<br />
<br />
'''Prerequisites For Using The AdIface.GetNielsenContentData() Roku API'''<br />
<br />
* Roku Ad Framework (RAF) must be implemented:<br />
** <code>bs_libs_required=roku_ads_lib</code> must be included in the Roku manifest file.<br />
** <code>Library "Roku_Ads.brs"</code> should be the first entry in your Roku main.brs file.<br />
** <code>adIface = Roku_Ads()</code> allows you to access the Roku Ad Framework interface and must be called before utilizing RAF API's such as <code>AdIface.GetNielsenContentData()</code><br />
* For additional details for integrating the Roku Ad Framework (RAF) please refer to the link provided below:<br />
** https://sdkdocs.roku.com/display/sdkdoc/Integrating+the+Roku+Advertising+Framework<br />
<br />
===== Request N-RIDA for the devInfo Payload encdata parameter using AdIface.GetNielsenContentData() =====<br />
<br />
'''Example Code For Configuring Encdata'''<br />
<syntaxhighlight lang="java">// create a variable as a placeholder for the N-RIDA payload<br />
n_Rida=AdIface.GetNielsenContentData()<br />
<br />
// populate the encdata key in devInfo with the AdIface.GetNielsenContentData() retrun<br />
"devInfo": {<br />
"apn": "AppName",<br />
"apv": "1.0",<br />
"encdata": "n_Rida", //insert the AdIface.GetNielsenContentData() return within encdata<br />
"uoo": "false"<br />
}</syntaxhighlight><br />
<br />
<blockquote>'''Note''': When pulled and populated into the encdata key, the <code>AdIface.GetNielsenContentData()</code> return will be formatted similarly to the example below, and will include "encdata=" within the return along with the N-RIDA key.</blockquote><br />
<syntaxhighlight lang="json">"encdata":"encdata=v1.key.RokuAES256CBC00.iv.HSh88AM%2BG%2Fx57TZ%2BW5j5Fw%3D%3D..."</syntaxhighlight><br />
<br />
==== 3.2 Configure Payload: metadata ====<br />
Asset metadata can be passed through <code>"metadata"</code>. There are two asset types: <code>"content"</code> for video and <code>"ad"</code> for ads. The metadata received for each asset is used for classification and reporting.<br />
<br />
You will need to set up <code>"metadata"</code> objects for <code>"content"</code> and <code>"ad"</code> with the required Nielsen keys as shown in the sample code below.<br />
<br />
===== Content Metadata =====<br />
Content metadata should remain constant throughout the entirety of an episode/clip including when ads play.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| type || type of asset || <code>"content"</code> || Yes<br />
|-<br />
| assetid || unique ID assigned to asset || custom || Yes<br />
|-<br />
| program ||name of program (25 character limit) || custom || Yes<br />
|-<br />
| title ||name of program (25 character limit) || custom || Yes<br />
|-<br />
| length || length of content in seconds || <code>seconds</code> (0 for live stream) || Yes<br />
|-<br />
| segB || custom segment B || custom || No<br />
|-<br />
| segC || custom segment C || custom || No<br />
|-<br />
| airdate || the airdate in the linear TV || YYYYMMDD HH24:MI:SS || Yes<br />
|-<br />
| isfullepisode || full episode flag || <code>"y"</code>- full episode, <code>"n"</code>- non full episode || Yes<br />
|-<br />
| crossId1 || standard episode ID || custom || Yes<br />
|-<br />
| crossId2 || content originator (only required for distributors) || Nielsen || No<br />
|-<br />
| adloadtype || type of ad load:<br />
<code>"1"</code> Linear – matches TV ad load<br />
<br />
<code>"2"</code> Dynamic – Dynamic Ad Insertion (DAI)<br />
|| <code>"2"</code> - DCR measures content with dynamic ads || Yes<br />
|-<br />
| hasAds || ads indicator<br />
<code>"1"</code>: ads included<br />
<br />
<code>"0"</code>: ads not included<br />
|| <code>"1"</code> or <code>"0"</code> || Yes<br />
|-<br />
| subbrand || sub brand override || Nielsen || No<br />
|-<br />
| progen || program genre abbreviation - see [[DCR OTT Genre List]] for accepted values || <code>"CV"</code> for Comedy Variety || Yes<br />
|}<br />
<br />
<br />
'''Example Content Object'''<br />
<syntaxhighlight lang='json'>// create content object<br />
"content": {<br />
"type": "content",<br />
"assetid": "VIDEO-ID123",<br />
"isfullepisode": "y",<br />
"program": "Program Name",<br />
"title": "Episode Title S3 - EP1",<br />
"length": "1800",<br />
"segB": "Custom Segment B",<br />
"segC": "Custom Segment C",<br />
"crossId1": "Standard Episode ID",<br />
"crossId2": "Content Originator ID",<br />
"airdate": "20161013 20:00:00",<br />
"adloadtype": "2",<br />
"hasAds": "1", <br />
"subbrand": "c05",<br />
"progen": "CV"<br />
}</syntaxhighlight><br />
<br />
===== Ad Metadata =====<br />
The ad metadata should be passed for each individual ad.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| type || type of ad || <code>"preroll"</code>, <code>"midroll"</code>, or <code>"postroll"</code> || Yes<br />
|-<br />
| assetid || unique ID assigned to ad || custom || Yes<br />
|}<br />
<br />
===== Example Ad Object =====<br />
<syntaxhighlight lang="javascript"><br />
// create ad object<br />
"ad": {<br />
"type": "preroll",<br />
"assetid": "AD-ID123"<br />
}<br />
</syntaxhighlight><br />
<br />
=== Configure Payload: Events ===<br />
<br />
The last part of the payload is for enabling events so content is measured correctly when viewed. The events and required parameters are included below.<br />
<br />
==== Event Types ====<br />
<br />
The available events are:<br />
{| class="wikitable"<br />
|-<br />
! Event !! Description<br />
|-<br />
| <code>"playhead"</code> || Playhead position in seconds, must be passed as a whole number every 10 seconds. The final playhead position should be sent before an asset has changed to properly capture full duration. When content is paused, stop passing playhead position until content is resumed. On playhead scrubbing, send current playhead position, followed by the playhead position the user scrubs to. For Live streams, you may use Unix Time (in seconds) as the playhead position. Note that ad playheads must also use Unix Time if Unix Time is used for content playheads.<br />
|-<br />
| <code>"complete"</code> || The complete event must be sent when the content has completed full playback. Before calling the complete event, a final playhead update with the final position is required to be sent to receive full duration credit. For Live streams, a complete event must be sent at program boundaries.<br />
|-<br />
| <code>"delete"</code> || The delete event is optional and can be sent when the viewing session is terminated (typically on App close). A new session ID must be generated after sending a delete event. Delete should not be sent on app interruptions or foreground/background events. All creditable duration will be summarized for all asset types when delete occurs (content and ads).<br />
|}<br />
<br />
===== Event Parameters =====<br />
<br />
The following parameters need to be passed when calling events:<br />
<br />
{| class="wikitable"<br />
|-<br />
! Parameter !! Description !! Value !! Required<br />
|-<br />
| <code>"event"</code> || event type || <code>"playhead"</code>, <code>"complete"</code>, or <code>"delete"</code> || Yes<br />
|-<br />
| <code>"position"</code> || creditable position || playhead position in seconds or Unix Time for live stream || Yes<br />
|-<br />
| <code>"type"</code> || asset type || <code>"content"</code>, <code>"ad"</code> || Yes<br />
|-<br />
| <code>"utc"</code> || Unix timestamp. Must be passed every 10 seconds. || <code>"1472760000000"</code> || Yes<br />
|}<br />
<br />
===== Example Event =====<br />
You can call events by passing values in the required parameters:<br />
<br />
<syntaxhighlight lang="javascript"><br />
"devInfo": [deviceInfo],<br />
"metadata": {<br />
"static": [static metadata],<br />
"content": [content metadata],<br />
"ad": [ad metadata]<br />
},<br />
// Event Parameters<br />
"event": [event], // event name<br />
"position": [playheadPosition], //position in seconds<br />
"type": [asset type], // values are "content" or "ad"<br />
"utc": "1472760000000" //unix timestamp<br />
}<br />
</syntaxhighlight><br />
<br />
'''Note:''' The full payload including "devInfo" and "metadata" must be populated in each event request.<br />
<br />
===Example Image Request===<br />
<br />
In order to execute Cloud API calls, image requests will need to be established for each event.<br />
<br />
===Playhead Image Request Example Code===<br />
<br />
<syntaxhighlight lang="javascript"><br />
playheadRequest = CreateObject("roUrlTransfer")<br />
sendUrl = sessionUrl+playheadRequest.Escape(playheadPayload)<br />
playheadRequest.SetUrl(sendUrl)<br />
xport=CreateObject("RoMessagePort")<br />
playheadRequest.setport(xport)<br />
aa3 = {}<br />
aa3["Connection"] = "keep-alive"<br />
aa3["Content-type"] = "image/gif"<br />
playheadRequest.SetHeaders(aa3)<br />
playheadRequest.setRequest("GET")<br />
</syntaxhighlight><br />
<br />
===== Sample Event Lifecycle =====<br />
The sample event lifecycle can be used as a reference for identifying the order for calling events and values to pass.<br />
<br />
<syntaxhighlight lang="javascript"><br />
// Start of Session: session ID created when App is opened<br />
<br />
// Preroll<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
// Content<br />
Content Playhead {"event": "playhead", "position": "0", "type": "content", "utc": "1472760000000"} <br />
<br />
// Midroll<br />
Midroll Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
// Content resumes at 15 minutes<br />
Content Playhead {"event": "playhead", "position": "900", "type": "content", "utc": "1472760000000"} <br />
<br />
// Content completes at 30 minutes<br />
Complete {"event": "complete", "position": "1800", "type": "content", "utc": "1472760000000"} <br />
<br />
// Postroll<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
//End of Session: The delete event should be called when the App is exited. The values for position and type not required to be passed.<br />
Delete { "event": "delete", "position": "", "type": "", "utc": "1472760000000"} <br />
</syntaxhighlight><br />
<br />
<br />
'''Sample Event Lifecycle - Detailed Storyline'''<br />
This detailed event sequence provides additional insight for the correct events to call when handling certain playback scenarios.<br />
<syntaxhighlight lang='javascript'>// SESSION STARTS<br />
// Start of Session: session ID created when App is opened<br />
<br />
// PREROLL<br />
// Preroll Start - Start each Ad with a position of "0", resetting to '0' for each Ad, and Ad break.<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
// Preroll Stop - End each Ad with the final position of the Ad.<br />
Ad Playhead {"event": "playhead", "position": "15", "type": "ad", "utc": "1472761500000"}<br />
<br />
// CONTENT <br />
// Content Start - Start new content streams with a position of "0" incrementing the position every 10 seconds.<br />
Content Playhead {"event": "playhead", "position": "0", "type": "content", "utc": "1472761500000"} <br />
<br />
// Content Stop Before Ad Break - Send a playhead update including the current content positon before an Ad break.<br />
Content Playhead {"event": "playhead", "position": "299", "type": "content", "utc": "1472787400000"}<br />
<br />
// MIDROLL<br />
// Midroll Start - Start each Ad with a position of "0", resetting to '0' for each Ad, and Ad break.<br />
Midroll Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472787500000"} <br />
<br />
// Midroll Stop - End each Ad with the final position of the Ad.<br />
Ad Playhead {"event": "playhead", "position": "60", "type": "ad", "utc": "1472793500000"}<br />
<br />
// CONTENT<br />
// Content resumes at 5 minutes - Send playhead update with the current resumed position, and begin incrimenting the positon every 10 seconds.<br />
Content Playhead {"event": "playhead", "position": "300", "type": "content", "utc": "1472799500000"} <br />
<br />
// Content completes at 10:12 - Make sure to send in the playhead event with the final content position before sending the complete event.<br />
Final Content Playhead {"event": "playhead", "position": "612", "type": "content", "utc": "1472830700000"} <br />
<br />
Complete {"event": "complete", "position": "612", "type": "content", "utc": "1472830800000"} <br />
<br />
// POSTROLL<br />
// Postroll Start - Start each Ad with a position of "0", resetting to '0' for each Ad, and Ad break.<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472830900000"} <br />
<br />
// Postroll Stop - End each Ad with the final position of the Ad.<br />
Ad Playhead {"event": "playhead", "position": "45", "type": "ad", "utc": "1472835300000"}<br />
<br />
// SESSION ENDS<br />
<br />
//End of Session: The delete event should be called when the App is exited. The values for position and type not required to be passed.<br />
Delete { "event": "delete", "position": "", "type": "", "utc": "1472835400000"} </syntaxhighlight><br />
<br />
<br />
=====Handling Playhead=====<br />
Calling <code>"playhead"</code> is critical for accurate duration crediting. You can reference the below guidance to determine the correct playhead position to pass depending on the playback scenario.<br />
<br />
'''Playhead: General'''<br />
* Playhead position must start at 0 for each new asset, and be passed at least every 10 seconds.<br />
* Final postion must be sent at the end of content or an ad<br />
<br />
'''Playhead: Ads'''<br />
* The final position must be sent when switching from content to ad, or ad to content.<br />
* Each ad playhead position should be 0 at ad start.<br />
* For Ad Pods, playhead must be called, and reset to 0 for each individual ad. <br />
* The last content position before an Ad should be sent before switching to Ads.<br />
* When content has resumed following an ad break, the playhead position update must continue where the previous content segment left off.<br />
<br />
'''Playhead: User Actions'''<br />
* Upon user scrubbing, the current position must be sent before a user scrubs, and the new position should be sent where the user lands, and begin sending in the 10 second updates thereafter.<br />
* On pause, send the current position and then discontinue sending playhead event updates.<br />
* If a user exits a stream early, the last current position must be sent in a playhead update to receive accurate duration.<br />
<br />
===== Interruption Scenarios =====<br />
<br />
As part of configuring events, you will need to handle all possible interruption scenarios such as:<br />
<br />
*Wi-Fi OFF / ON<br />
*App going Background / Foreground (Video players only, not for Audio players)<br />
*App Crash or Exit<br />
<br />
When playback is interrupted, the app needs to send delete immediately.<br />
<br />
Once playback resumes, a new session will need to be created with a unique session ID. All of the required metadata and events will need to be sent.<br />
<br />
'''Note:''' The session will automatically timeout after 30 minutes of inactivity.<br />
<br />
=== Example Request ===<br />
<br />
Now that we walked through the Cloud API integration steps, your requests should have the following components: Session ID, App ID, and Payload. You can reference the example below when your reviewing your integration.<br />
<br />
<syntaxhighlight lang="javascript"><br />
// 1. Create Session ID<br />
sessionID = dfc7dc6a-66a7-4705-9fba-adaaf7e3d5e0 // Example sessionID created using a UUID Generator<br />
<br />
// 2. Define URL Structure with App ID and Session ID<br />
sessionURL = https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=<br />
<br />
// 3. Configure Payload<br />
// 3.1 Configure Payload: devInfo <br />
payload = {<br />
"devInfo": {<br />
"encdata": "encdata=v1.key.RokuAES256CB...",<br />
"apn": "AppName",<br />
"apv": "1.0",<br />
"uoo": "false"<br />
},<br />
<br />
// 3.2 Configure Payload: metadata<br />
"metadata": {<br />
"static": {}, // object for measuring static content<br />
"content": { // object for measuring video content<br />
"type": "content", // "content" for video<br />
"assetid": "VIDEO-ID123", // unique ID for video<br />
"isfullepisode": "y", // full episode flag<br />
"program": "Program Name", // program name<br />
"title": "Episode Title S3 - EP1", // episode name<br />
"length": "1800", // content duration in seconds<br />
"segB": "Custom Segment B", // custom segment<br />
"segC": "Custom Segment C", // custom segment<br />
"crossId1": "Standard Episode ID", // episode ID<br />
"crossId2": "Content Originator ID", // content orginator (required for distributors)<br />
"airdate": "20161013 20:00:00", // airdate<br />
"adloadtype": "2", // ad load flag<br />
"hasAds": "1", // content contains ads = 1 / no ads = 0<br />
"progen": "CV" // program genre abbreviation<br />
},<br />
"ad": {<br />
"type": "preroll", // type of ad<br />
"assetid": "AD-ID123" // unique ID for ad<br />
}<br />
},<br />
<br />
// 3.3 Configure Payload: events<br />
"event": "playhead", //event name<br />
"position": "300", // position in seconds<br />
"type": "content", //"content" or "ad"<br />
"utc": "1456448742000" //unix timestamp in milliseconds <br />
}<br />
<br />
// Append payload to URL<br />
sendUrl = sessionUrl.Escape(payload)<br />
</syntaxhighlight><br />
<br />
==== Enable Debug Logging ====<br />
<br />
Now that you have set up the Cloud API requests, you can enable debug logging to validate your integration. Enabling debug logging is required for Nielsen certification.<br />
<br />
==== GET Request ====<br />
<br />
Display GET Request to console using a name to identify each event (e.g. playhead).<br />
<br />
<syntaxhighlight lang="javascript"><br />
print "Playhead Event " + sendUrl<br />
</syntaxhighlight><br />
<br />
==== Payload ====<br />
<br />
Output payload to identify required metadata and events.<br />
<br />
<syntaxhighlight lang="javascript"><br />
print "Event Payload "+ playheadPayload + modSeq%.tostr()<br />
</syntaxhighlight><br />
<br />
==== HTTP Response Code ====<br />
<br />
Confirm request was completed by viewing HTTP response code.<br />
<br />
<syntaxhighlight lang="javascript"><br />
httpCode = GetResponseCode()<br />
print "Response Code " +httpCode.tostr()<br />
</syntaxhighlight><br />
<br />
You can reference the HTTP Response Code table when reviewing your requests:<br />
<br />
{| class="wikitable"<br />
|-<br />
! Status Code !! Status Text !! Description<br />
|-<br />
| <code>200</code> || OK || request received<br />
|-<br />
| <code>403</code> || Forbidden || invalid App ID<br />
|-<br />
| <code>404</code> || Not Found || JSON issue<br />
|}<br />
<br />
== Opt-Out ==<br />
<br />
Your app must provide a means for the user to Opt-Out, or Opt-In to Nielsen Measurement. This can be implemented in two steps:<br />
<br />
==== Step 1: Nielsen Privacy Policy & Roku Channel Disclosure ====<br />
<br />
In your application, you must display the Nielsen privacy policy, which instructs users on how to opt out and opt in to Nielsen measurements. This text is usually displayed in an application's "Settings" or "About" screens.<br />
<br />
<blockquote><br />
'''''ABOUT NIELSEN MEASUREMENT'''''<br />
<br />
Television and the way we watch it have come a long way since Nielsen began measuring TV audiences in 1950. Today, the ability to watch our favorite shows at any time and on multiple devices amplifies the need for exceptionally adept and flexible audience measurement capabilities.<br />
<br />
Consumers are changing with the times, and the same goes for us. As technology continues to evolve and media companies try new ways to attract viewers, understanding what consumers are watching — and what they're watching on — is more important than ever. Today, viewing video is a personal and mobile experience — anytime and anywhere. Our capabilities provide relevant metrics that are necessary to inform successful marketing and programming and drive continued growth. As a global information and measurement leader, we are committed to protecting the privacy and security of the data we collect, process and use. While our digital measurement products are not used to identify you in any way, they help us and our clients measure and analyze how consumers engage with media across online, mobile and emerging technologies, and offer insights into consumer behavior.<br />
<br />
'''''YOUR CHOICES'''''<br />
<br />
Nielsen believes that you should have a choice about whether to contribute to our research and insights. To opt out, or opt into Nielsen measurement please choose the appropriate "Limit Ad Tracking" setting on your device. If you have this app on more than one device, you will need to opt out of this app on each device. To learn more about our digital measurement products and your choices in regard to them, please visit http://priv-policy.imrworldwide.com/priv/browser/us/en/optout.html.<br />
</blockquote><br />
<br />
In addition, on your Roku Channel description, you should add the following disclosure:<br />
<br />
<blockquote><br />
This app features Nielsen's proprietary measurement software which will allow you to contribute to market research, like Nielsen's TV Ratings. Please see http://priv-policy.imrworldwide.com/priv/browser/us/en/optout.html for more information.<br />
</blockquote><br />
<br />
==== Step 2: Use "Limit Ad Tracking" setting to set User Opt Out ====<br />
<br />
Depending on the user's selection for the "Limit Ad Tracking" device setting, you should set the Cloud API <code>"uoo"</code> parameter accordingly. Roku provides an API called [https://sdkdocs.roku.com/display/sdkdoc/ifDeviceInfo#ifDeviceInfo-IsAdIdTrackingDisabled()asBoolean IsAdIdTrackingDisabled()] to check the user's limit ad tracking setting.<br />
<br />
{| class="wikitable"<br />
|-<br />
! uoo Key !! Description !! Values<br />
|-<br />
| uoo || Device is Opted-In to Nielsen Measurement || <code>"false"</code><br />
|-<br />
| uoo || Device is Opted-Out of Nielsen Measurement || <code>"true"</code><br />
|}<br />
<br />
The <code>"uoo"</code> parameter is located in the <code>"devInfo"</code> JSON object, which will be sent in every Cloud API event (playhead, complete, & delete).<br />
<br />
===== devInfo Opt-In JSON Payload Example =====<br />
<br />
<syntaxhighlight lang="javascript"><br />
"devInfo": {<br />
"apn": "Roku Sample App",<br />
"apv": "1",<br />
"encdata": "encdata=v1.key.RokuAES256CB...",<br />
"uoo": "false"<br />
},<br />
</syntaxhighlight><br />
<br />
===== devInfo Opt-Out JSON Payload Example =====<br />
<br />
<syntaxhighlight lang="javascript"><br />
"devInfo": {<br />
"apn": "Roku Sample App",<br />
"apv": "1",<br />
"encdata": "", //Encdata must be blank when a user elects to Opt-Out.<br />
"uoo": "true"<br />
},<br />
</syntaxhighlight><br />
<br />
Your application should check the Limit Ad Tracking setting on each app start, and/or periodically, to ensure the user's ad tracking selection is reflected in the Nielsen opt-out setting in your app.<br />
<br />
*In addition, you will need to ensure that the encdata field is populated with a blank value if a user has elected to Opt-Out.<br />
<br />
== Testing ==<br />
Before providing an app build to Nielsen for testing, it is important to run validation checks once you have enabled debug logging.<br />
<br />
=== Payload Validation ===<br />
<br />
Ensure that all of the required payload data is populating while testing several videos. The following areas are critical to measurement:<br />
*devInfo<br />
*Asset metadata for both content, and ads<br />
*Events<br />
*Opt-Out status<br />
<br />
=== Player Events ===<br />
Review event calls:<br />
<br />
==== playhead ====<br />
*Playhead position updates every 10 seconds starting at position '0' for each new asset.<br />
*Final playhead position is sent on content, or ad before switching between assets.<br />
*Content metadata remains constant throughout an episode, or clip play.<br />
*Ad metadata is populated appropriately for each individual ad.<br />
*Playhead position update resumes for content after an ad break, and resets to 0 for each individual ad.<br />
*For scrubbing, last current position should be sent while scrubbing occurs, and the new position should also be sent where the user scrubs to.<br />
*Exiting a stream early should execute the last current position in a playhead update to receive accurate duration.<br />
*Upon pause, the current position should be sent, and playhead updates should stop incrementing until resume play occurs.<br />
<br />
==== complete ====<br />
*Check that the complete event executes upon content complete after the final playhead update is sent.<br />
*Do not execute the complete event for ads<br />
<br />
==== delete ====<br />
*Check to see that the delete event occurs upon app exit<br />
<br />
==== GET Request Format ====<br />
*Ensure that the event payloads are formatted in JSON<br />
*Check to see that each of the Cloud API GET requests are properly encoded.<br />
<br />
==== HTTP Response ====<br />
*Make sure that each of the Cloud API Get requests are received by the Nielsen Cloud API properly through use of the HTTP Response Code outputs enabled in console.<br />
<br />
==== Opt-Out ====<br />
*Test the "uoo" key gets populated accurately for both Opt-In and Opt-Out selections based on the device's "Limit Ad Tracking" setting.<br />
*Test that the encdata field is populated with a blank value if a user has elected to Opt-Out. For Example: "encdata": "",<br />
<br />
== Go Live ==<br />
After your integration has been certified, you will need to: Change Endpoint and Disable Logging.<br />
<br />
'''Change Endpoint:''' You will need to update to the production endpoint:<br />
<br />
*Testing: <code>https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/</code><br />
*Production: <code>https://cloudapi.imrworldwide.com/nmapi/v2/</code><br />
<br />
Your production URL structure should now be:<br />
<code>https://cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=[payload]</code><br />
<br />
'''Disable Logging:''' You can now disable debug logging.</div>AlexGutierrezhttps://engineeringportal.nielsen.com//w/index.php?title=DCR_Video_%26_Static_Cloud_API&diff=3632DCR Video & Static Cloud API2019-05-03T16:53:50Z<p>AlexGutierrez: </p>
<hr />
<div>{{Breadcrumb|}} {{Breadcrumb|Digital}} {{Breadcrumb|DCR & DTVR}} {{CurrentBreadcrumb}}<br />
[[Category:Digital]]<br />
<br />
This guide shows you how to integrate the Nielsen Cloud API to enable Digital Content Ratings (DCR) measurement on your over-the-top (OTT) Apps.<br />
*For Roku Apps, please see [[DCR Video & Static Roku Cloud API]]<br />
*For Mobile Apps, please see [[DCR Video & Static Mobile Cloud API]]<br />
<br />
==Prerequisites==<br />
To get started, you will need a Nielsen App ID. The App ID is a unique ID assigned to your app. This will be provided to you upon starting the integration.<br />
<br />
<syntaxhighlight lang="javascript">XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX</syntaxhighlight><br />
<br />
==Integration==<br />
We will cover the steps for constructing the Cloud API Calls.<br />
<br />
===Request Overview===<br />
<br />
====URL Structure====<br />
<br />
The Cloud API Calls are HTTP GET Requests with the URL structure:<br />
<br />
<syntaxhighlight lang="javascript">[endpoint]/[appid]/[sessionID]/a?b=[payload]</syntaxhighlight><br />
<br />
The URL includes the following components:<br />
<br />
*<code>[endpoint]</code>: location of data collection environment<br />
*<code>[appid]</code>: provided App ID<br />
*<code>[sessionID]</code>: unique value for each user session<br />
*<code>[payload]</code>: metadata and events<br />
<br />
====Endpoint====<br />
<br />
There are endpoints for testing and production:<br />
<br />
*Testing: <code>https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/</code><br />
*Production: <code>https://cloudapi.imrworldwide.com/nmapi/v2/</code><br />
<br />
During testing, all calls should be pointed to the testing endpoint. We will review the update to the production endpoint during the Go Live section of this guide.<br />
<br />
====URL Example====<br />
As you move through the integration steps, you can reference the below URL structure with the expanded payload:<br />
<br />
<syntaxhighlight lang="javascript"><br />
https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=<br />
{<br />
"devInfo": [deviceInfo],<br />
"metadata": {<br />
"static": [static_metadata],<br />
"content": [content_metadata],<br />
"ad": [ad metadata]<br />
},<br />
"event": [event],<br />
"position": [playhead_position],<br />
"type": [asset type],<br />
"utc": [Unix time in ms]<br />
}<br />
</syntaxhighlight><br />
<br />
===Create Session ID===<br />
A unique Session ID must be created upon app launch and provided in the URL. This will allow measurement to occur for the entire duration that a user is within the app.<br />
<br />
A Session ID needs to be completely unique so it is recommended to use a version 4 UUID or another method of your choosing to guarantee there are no repeats.<br />
<br />
Upon exiting the app, the session will need to be terminated using the delete event. Sessions will automatically expire after 30 minutes of cloud inactivity.<br />
<br />
===Define URL Structure===<br />
Define the URL structure using your provided <code>[appid]</code> and a unique <code>[sessionID]</code>.<br />
<br />
<syntaxhighlight lang="javascript">https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=[payload]</syntaxhighlight><br />
<br />
===Configure Payload===<br />
<br />
All Cloud API requests must contain the following payload data:<br />
<br />
*''devInfo'': device and app info<br />
*''metadata'': asset metadata<br />
*''event metadata'': type of event<br />
<br />
The payload can be passed through key-values using the Nielsen reserved keys. The specific keys and descriptions are highlighted in the tables included in this section.<br />
<br />
'''Payload Example'''<br />
<br />
The example below should be referenced when following the steps for configuring the request payload.<br />
<br />
<syntaxhighlight lang="javascript"><br />
// 3.1 Configure Payload: devInfo <br />
payload = {<br />
"devInfo": {<br />
"devId": "AD-ID", <br />
"apn": "AppName",<br />
"apv": "1.0",<br />
"uoo": "false"<br />
},<br />
<br />
// 3.2 Configure Payload: metadata<br />
"metadata": {<br />
"static": {}, // object for measuring static content<br />
"content": { // object for measuring video content<br />
"type": "content", // "content" for video<br />
"assetid": "VIDEO-ID123", // unique ID for video<br />
"isfullepisode": "y", // full episode flag<br />
"program": "Program Name", // program name<br />
"title": "Episode Title S3 - EP1", // episode name<br />
"length": "1800", // content duration in seconds<br />
"segB": "Custom Segment B", // custom segment<br />
"segC": "Custom Segment C", // custom segment<br />
"crossId1": "Standard Episode ID", // episode ID<br />
"crossId2": "Content Originator ID", // content orginator (required for distributors)<br />
"airdate": "20161013 20:00:00", // airdate<br />
"adloadtype": "2" //ad load flag<br />
"hasAds": "1", // content contains ads = 1 / no ads = 0<br />
"progen": "CV" // program genre abbreviation<br />
},<br />
"ad": {<br />
"type": "preroll", // type of ad<br />
"assetid": "AD-ID123" // unique ID for ad<br />
}<br />
},<br />
<br />
// 3.3 Configure Payload: events<br />
"event": "playhead", //event name<br />
"position": "300", // position in seconds<br />
"type": "content", //"content" or "ad"<br />
"utc": "1456448742000" //unix timestamp in milliseconds <br />
}<br />
</syntaxhighlight><br />
<br />
=====Configure Payload: devInfo=====<br />
An object <code>"devInfo"</code> will need to be created to capture App and Device information.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| devId || unique ID to identify user (e.g. Advertising ID, Roku Device ID) || custom || Yes<br />
|-<br />
| apn || app name || custom || Yes<br />
|-<br />
| apv || app build version || custom || Yes<br />
|-<br />
| uoo || device opt-out status || <code>"true"</code> or <code>"false"</code> || Yes<br />
|-<br />
|}<br />
<br />
'''Example devInfo Object'''<br />
<syntaxhighlight lang="javascript"><br />
// create devInfo object<br />
"devInfo": {<br />
"devId": "AD-ID",<br />
"apn": "AppName",<br />
"apv": "1.0",<br />
"uoo": "false"<br />
},<br />
</syntaxhighlight><br />
<br />
==== 3.2 Configure Payload: metadata ====<br />
Asset metadata can be passed through <code>"metadata"</code>. There are two asset types: <code>"content"</code> for video and <code>"ad"</code> for ads. The metadata received for each asset is used for classification and reporting.<br />
<br />
You will need to set up <code>"metadata"</code> objects for <code>"content"</code> and <code>"ad"</code> with the required Nielsen keys as shown in the sample code below.<br />
<br />
===== Content Metadata =====<br />
Content metadata should remain constant throughout the entirety of an episode/clip including when ads play.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| type || type of asset || <code>"content"</code> || Yes<br />
|-<br />
| assetid || unique ID assigned to asset || custom || Yes<br />
|-<br />
| program ||name of program (25 character limit) || custom || Yes<br />
|-<br />
| title ||name of program (25 character limit) || custom || Yes<br />
|-<br />
| length || length of content in seconds || <code>seconds</code> (0 for live stream) || Yes<br />
|-<br />
| segB || custom segment B || custom || No<br />
|-<br />
| segC || custom segment C || custom || No<br />
|-<br />
| airdate || the airdate in the linear TV || YYYYMMDD HH24:MI:SS || Yes<br />
|-<br />
| isfullepisode || full episode flag || <code>"y"</code>- full episode, <code>"n"</code>- non full episode || Yes<br />
|-<br />
| crossId1 || standard episode ID || custom || Yes<br />
|-<br />
| crossId2 || content originator (only required for distributors) || Nielsen || No<br />
|-<br />
| adloadtype || type of ad load:<br />
<code>"1"</code> Linear – matches TV ad load<br />
<br />
<code>"2"</code> Dynamic – Dynamic Ad Insertion (DAI)<br />
|| <code>"2"</code> - DCR measures content with dynamic ads || Yes<br />
|-<br />
| hasAds || ads indicator<br />
<code>"1"</code>: ads included<br />
<br />
<code>"0"</code>: ads not included<br />
|| <code>"1"</code> or <code>"0"</code> || Yes<br />
|-<br />
| subbrand || sub brand override || Nielsen || No<br />
|-<br />
| progen || program genre abbreviation - see [[DCR OTT Genre List]] for accepted values || <code>"CV"</code> for Comedy Variety || Yes<br />
|}<br />
<br />
<br />
'''Example Content Object'''<br />
<syntaxhighlight lang='json'>// create content object<br />
"content": {<br />
"type": "content",<br />
"assetid": "VIDEO-ID123",<br />
"isfullepisode": "y",<br />
"program": "Program Name",<br />
"title": "Episode Title S3 - EP1",<br />
"length": "1800",<br />
"segB": "Custom Segment B",<br />
"segC": "Custom Segment C",<br />
"crossId1": "Standard Episode ID",<br />
"crossId2": "Content Originator ID",<br />
"airdate": "20161013 20:00:00",<br />
"adloadtype": "2",<br />
"hasAds": "1", <br />
"subbrand": "c05",<br />
"progen": "CV"<br />
}</syntaxhighlight><br />
<br />
===== Ad Metadata =====<br />
The ad metadata should be passed for each individual ad.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| type || type of ad || <code>"preroll"</code>, <code>"midroll"</code>, or <code>"postroll"</code> || Yes<br />
|-<br />
| assetid || unique ID assigned to ad || custom || Yes<br />
|}<br />
<br />
===== Example Ad Object =====<br />
<syntaxhighlight lang="javascript"><br />
// create ad object<br />
"ad": {<br />
"type": "preroll",<br />
"assetid": "AD-ID123"<br />
}<br />
</syntaxhighlight><br />
<br />
=== Configure Payload: Events ===<br />
<br />
The last part of the payload is for enabling events so content is measured correctly when viewed. The events and required parameters are included below.<br />
<br />
==== Event Types ====<br />
<br />
The available events are:<br />
{| class="wikitable"<br />
|-<br />
! Event !! Description<br />
|-<br />
| <code>"playhead"</code> || Playhead position in seconds, must be passed as a whole number every 10 seconds. The final playhead position should be sent before an asset has changed to properly capture full duration. When content is paused, stop passing playhead position until content is resumed. On playhead scrubbing, send current playhead position, followed by the playhead position the user scrubs to. For Live streams, you may use Unix Time (in seconds) as the playhead position. Note that ad playheads must also use Unix Time if Unix Time is used for content playheads.<br />
|-<br />
| <code>"complete"</code> || The complete event must be sent when the content has completed full playback. Before calling the complete event, a final playhead update with the final position is required to be sent to receive full duration credit. For Live streams, a complete event must be sent at program boundaries.<br />
|-<br />
| <code>"delete"</code> || The delete event is optional and can be sent when the viewing session is terminated (typically on App close). A new session ID must be generated after sending a delete event. Delete should not be sent on app interruptions or foreground/background events. All creditable duration will be summarized for all asset types when delete occurs (content and ads).<br />
|}<br />
<br />
===== Event Parameters =====<br />
<br />
The following parameters need to be passed when calling events:<br />
<br />
{| class="wikitable"<br />
|-<br />
! Parameter !! Description !! Value !! Required<br />
|-<br />
| <code>"event"</code> || event type || <code>"playhead"</code>, <code>"complete"</code>, or <code>"delete"</code> || Yes<br />
|-<br />
| <code>"position"</code> || playhead position in seconds or Unix time in seconds || <code>"300"</code> || Yes<br />
|-<br />
| <code>"type"</code> || asset type || <code>"content"</code>, <code>"ad"</code> || Yes<br />
|-<br />
| <code>"utc"</code> || Unix timestamp in milliseconds. Must be passed every 10 seconds. || <code>"1472760000000"</code> || Yes<br />
|}<br />
<br />
===== Example Event =====<br />
You can call events by passing values in the required parameters:<br />
<br />
<syntaxhighlight lang="javascript"><br />
"devInfo": [deviceInfo],<br />
"metadata": {<br />
"static": [static metadata],<br />
"content": [content metadata],<br />
"ad": [ad metadata]<br />
},<br />
// Event Parameters<br />
"event": [event], // event name<br />
"position": [playheadPosition], //position in seconds<br />
"type": [asset type], // values are "content" or "ad"<br />
"utc": "1472760000000" //unix timestamp in milliseconds<br />
}<br />
</syntaxhighlight><br />
<br />
'''Note:''' The full payload including "devInfo" and "metadata" must be populated in each event request.<br />
<br />
===== Sample Event Lifecycle =====<br />
The sample event lifecycle can be used as a reference for identifying the order for calling events and values to pass.<br />
<br />
<syntaxhighlight lang="javascript"><br />
// Start of Session: session ID created when App is opened<br />
<br />
// Preroll<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
// Content<br />
Content Playhead {"event": "playhead", "position": "0", "type": "content", "utc": "1472760000000"} <br />
<br />
// Midroll<br />
Midroll Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
// Content resumes at 15 minutes<br />
Content Playhead {"event": "playhead", "position": "900", "type": "content", "utc": "1472760000000"} <br />
<br />
// Content completes at 30 minutes<br />
Complete {"event": "complete", "position": "1800", "type": "content", "utc": "1472760000000"} <br />
<br />
// Postroll<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
//End of Session: The delete event should be called when the App is exited. The values for position and type not required to be passed.<br />
Delete { "event": "delete", "position": "", "type": "", "utc": "1472760000000"} <br />
</syntaxhighlight><br />
<br />
<br />
'''Sample Event Lifecycle - Detailed Storyline'''<br />
This detailed event sequence provides additional insight for the correct events to call when handling certain playback scenarios.<br />
<syntaxhighlight lang='javascript'>// SESSION STARTS<br />
// Start of Session: session ID created when App is opened<br />
<br />
// PREROLL<br />
// Preroll Start - Start each Ad with a position of "0", resetting to '0' for each Ad, and Ad break.<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
// Preroll Stop - End each Ad with the final position of the Ad.<br />
Ad Playhead {"event": "playhead", "position": "15", "type": "ad", "utc": "1472761500000"}<br />
<br />
// CONTENT <br />
// Content Start - Start new content streams with a position of "0" incrementing the position every 10 seconds.<br />
Content Playhead {"event": "playhead", "position": "0", "type": "content", "utc": "1472761500000"} <br />
<br />
// Content Stop Before Ad Break - Send a playhead update including the current content positon before an Ad break.<br />
Content Playhead {"event": "playhead", "position": "299", "type": "content", "utc": "1472787400000"}<br />
<br />
// MIDROLL<br />
// Midroll Start - Start each Ad with a position of "0", resetting to '0' for each Ad, and Ad break.<br />
Midroll Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472787500000"} <br />
<br />
// Midroll Stop - End each Ad with the final position of the Ad.<br />
Ad Playhead {"event": "playhead", "position": "60", "type": "ad", "utc": "1472793500000"}<br />
<br />
// CONTENT<br />
// Content resumes at 5 minutes - Send playhead update with the current resumed position, and begin incrimenting the positon every 10 seconds.<br />
Content Playhead {"event": "playhead", "position": "300", "type": "content", "utc": "1472799500000"} <br />
<br />
// Content completes at 10:12 - Make sure to send in the playhead event with the final content position before sending the complete event.<br />
Final Content Playhead {"event": "playhead", "position": "612", "type": "content", "utc": "1472830700000"} <br />
<br />
Complete {"event": "complete", "position": "612", "type": "content", "utc": "1472830800000"} <br />
<br />
// POSTROLL<br />
// Postroll Start - Start each Ad with a position of "0", resetting to '0' for each Ad, and Ad break.<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472830900000"} <br />
<br />
// Postroll Stop - End each Ad with the final position of the Ad.<br />
Ad Playhead {"event": "playhead", "position": "45", "type": "ad", "utc": "1472835300000"}<br />
<br />
// SESSION ENDS<br />
<br />
//End of Session: The delete event should be called when the App is exited. The values for position and type not required to be passed.<br />
Delete { "event": "delete", "position": "", "type": "", "utc": "1472835400000"} </syntaxhighlight><br />
<br />
<br />
=====Handling Playhead=====<br />
Calling <code>"playhead"</code> is critical for accurate duration crediting. You can reference the below guidance to determine the correct playhead position to pass depending on the playback scenario.<br />
<br />
'''Playhead: General'''<br />
* Playhead position must start at 0 for each new asset, and be passed at least every 10 seconds.<br />
* Final postion must be sent at the end of content or an ad<br />
* Playheads should be sent in seconds only, not milliseconds<br />
<br />
'''Playhead: Ads'''<br />
* The final position must be sent when switching from content to ad, or ad to content.<br />
* Each ad playhead position should be 0 at ad start.<br />
* For Ad Pods, playhead must be called, and reset to 0 for each individual ad. <br />
* The last content position before an Ad should be sent before switching to Ads.<br />
* When content has resumed following an ad break, the playhead position update must continue where the previous content segment left off.<br />
<br />
'''Playhead: User Actions'''<br />
* Upon user scrubbing, the current position must be sent before a user scrubs, and the new position should be sent where the user lands, and begin sending in the 10 second updates thereafter.<br />
* On pause, send the current position and then discontinue sending playhead event updates.<br />
* If a user exits a stream early, the last current position must be sent in a playhead update to receive accurate duration.<br />
<br />
===== Interruption Scenarios =====<br />
<br />
As part of configuring events, you will need to handle all possible interruption scenarios such as:<br />
<br />
*Wi-Fi OFF / ON<br />
*App going Background / Foreground (Video players only, not for Audio players)<br />
*App Crash or Exit<br />
<br />
When playback is interrupted, the app needs to send delete immediately.<br />
<br />
Once playback resumes, a new session will need to be created with a unique session ID. All of the required metadata and events will need to be sent.<br />
<br />
'''Note:''' The session will automatically timeout after 30 minutes of inactivity.<br />
<br />
=== Example Request ===<br />
<br />
Now that we walked through the Cloud API integration steps, your requests should have the following components: Session ID, App ID, and Payload. You can reference the example below when your reviewing your integration.<br />
<br />
<syntaxhighlight lang="javascript"><br />
// 1. Create Session ID<br />
sessionID = dfc7dc6a-66a7-4705-9fba-adaaf7e3d5e0 // Example sessionID created using a UUID Generator<br />
<br />
// 2. Define URL Structure with App ID and Session ID<br />
sessionURL = https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=<br />
<br />
// 3. Configure Payload<br />
// 3.1 Configure Payload: devInfo <br />
payload = {<br />
"devInfo": {<br />
"devId": "AD-ID", <br />
"apn": "AppName",<br />
"apv": "1.0",<br />
"uoo": "false"<br />
},<br />
<br />
// 3.2 Configure Payload: metadata<br />
"metadata": {<br />
"static": {}, // object for measuring static content<br />
"content": { // object for measuring video content<br />
"type": "content", // "content" for video<br />
"assetid": "VIDEO-ID123", // unique ID for video<br />
"isfullepisode": "y", // full episode flag<br />
"program": "Program Name", // program name<br />
"title": "Episode Title S3 - EP1", // episode name<br />
"length": "1800", // content duration in seconds<br />
"segB": "Custom Segment B", // custom segment<br />
"segC": "Custom Segment C", // custom segment<br />
"crossId1": "Standard Episode ID", // episode ID<br />
"crossId2": "Content Originator ID", // content orginator (required for distributors)<br />
"airdate": "20161013 20:00:00", // airdate<br />
"adloadtype": "2", // ad load flag<br />
"hasAds": "1", // content contains ads = 1 / no ads = 0<br />
"progen": "CV" // program genre abbreviation<br />
},<br />
"ad": {<br />
"type": "preroll", // type of ad<br />
"assetid": "AD-ID123" // unique ID for ad<br />
}<br />
},<br />
<br />
// 3.3 Configure Payload: events<br />
"event": "playhead", //event name<br />
"position": "300", // position in seconds<br />
"type": "content", //"content" or "ad"<br />
"utc": "1456448742000" //unix timestamp in milliseconds <br />
}<br />
<br />
// Append payload to URL<br />
var image = new Image()<br />
image.onerror = function() {<br />
// wait and send again<br />
}<br />
(new Image).src = sessionURL+encodeURI(JSON.stringify(payload));<br />
</syntaxhighlight><br />
<br />
==== Enable Debug Logging ====<br />
<br />
Now that you have set up the Cloud API requests, you can enable debug logging to validate your integration. Enabling debug logging is required for Nielsen certification.<br />
<br />
==== GET Request ====<br />
<br />
Display GET Request to console using a name to identify each event (e.g. playhead).<br />
<br />
<syntaxhighlight lang="javascript"><br />
console.log("Event", image); <br />
</syntaxhighlight><br />
<br />
==== Payload ====<br />
<br />
Output payload to identify required metadata and events.<br />
<br />
<syntaxhighlight lang="javascript"><br />
console.log("Event Payload", payload); <br />
</syntaxhighlight><br />
<br />
==== HTTP Response Code ====<br />
<br />
Confirm request was completed by viewing HTTP response code.<br />
<br />
<syntaxhighlight lang="javascript"><br />
code = msg.GetResponseCode();<br />
console.log("Response Code", code); <br />
</syntaxhighlight><br />
<br />
You can reference the HTTP Response Code table when reviewing your requests:<br />
<br />
{| class="wikitable"<br />
|-<br />
! Status Code !! Status Text !! Description<br />
|-<br />
| <code>200</code> || OK || request received<br />
|-<br />
| <code>403</code> || Forbidden || invalid App ID<br />
|-<br />
| <code>404</code> || Not Found || JSON issue<br />
|}<br />
<br />
== Opt-Out ==<br />
Your app must provide a means for the user to Opt-Out, or Opt-In to Nielsen Measurement. This requirement can be fulfilled by checking the device OS for the user's setting of "Limit Ad Tracking" or similar option. If the device offers "Limit Ad Tracking" settings, you should set <code>uoo=true</code> or <code>uoo=false</code> depending on the user's privacy setting. Also, ensure that the <code>devId</code> is set to a blank value if the user elects to opt-out.<br />
<br />
If the device does not have OS-level "Do Not Track" settings, you can implement opt-out by creating an Opt-Out/Opt-In button, toggle switch, or slider within the app "Settings", or "About" section to allow the user selection.<br />
<br />
[[File:Nielsen Opt-Out.png|link=]]<br />
<br />
You will need to store the User Opt-Out (uoo) status, so that it can be retrieved and populated in the <code>"devInfo"</code> metadata which will be sent in every Cloud API event (playhead, complete, & delete). <br />
<br />
{| class="wikitable"<br />
|-<br />
! uoo Key !! Description !! Values<br />
|-<br />
| uoo || Device is Opted-In to Nielsen Measurement (Recommended Default Setting) || <code>"false"</code><br />
|-<br />
| uoo || Device is Opted-Out of Nielsen Measurement || <code>"true"</code><br />
|}<br />
<br />
===== devInfo Opt-In JSON Payload Example =====<br />
<br />
<syntaxhighlight lang="javascript"><br />
"devInfo": {<br />
"apn": "Cloud API Sample App",<br />
"apv": "1",<br />
"devId": "7be25cf9-8c40-5cc2-871e-19bf41940288",<br />
"uoo": "false"<br />
},<br />
</syntaxhighlight><br />
<br />
===== devInfo Opt-Out JSON Payload Example =====<br />
<br />
<syntaxhighlight lang="javascript"><br />
"devInfo": {<br />
"apn": "Cloud API Sample App",<br />
"apv": "1",<br />
"devId": "", //devId must be blank when a user elects to Opt-Out.<br />
"uoo": "true"<br />
},<br />
</syntaxhighlight><br />
<br />
===== Privacy Information Template To Include In Opt-Out Screen =====<br />
<br />
<br />
Additionally, all applications must display the Nielsen privacy policy within their application, typically in the Settings/About screen of your application. The Nielsen privacy policy text is listed below.<br />
<br />
<blockquote><br />
'''''ABOUT NIELSEN MEASUREMENT'''''<br />
<br />
Television and the way we watch it have come a long way since Nielsen began measuring TV audiences in 1950. Today, the ability to watch our favorite shows at any time and on multiple devices amplifies the need for exceptionally adept and flexible audience measurement capabilities.<br />
<br />
Consumers are changing with the times, and the same goes for us. As technology continues to evolve and media companies try new ways to attract viewers, understanding what consumers are watching — and what they're watching on — is more important than ever. Today, viewing video is a personal and mobile experience — anytime and anywhere. Our capabilities provide relevant metrics that are necessary to inform successful marketing and programming and drive continued growth. As a global information and measurement leader, we are committed to protecting the privacy and security of the data we collect, process and use. While our digital measurement products are not used to identify you in any way, they help us and our clients measure and analyze how consumers engage with media across online, mobile and emerging technologies, and offer insights into consumer behavior.<br />
<br />
'''''YOUR CHOICES'''''<br />
<br />
Nielsen believes that you should have a choice about whether to contribute to our research and insights. To opt out or opt in to Nielsen measurement, please toggle your "Limit Ad Tracking" (or similar setting) on your device, or make the appropriate selection within the application's settings. If you have this app on more than one device, you will need to opt out of this app on each device. To learn more about our digital measurement products and your choices in regard to them, please visit http://www.nielsen.com/digitalprivacy.<br />
<br />
'''''Disclosure Template'''''<br />
<br />
Additionally, you must update the App description information from the App Distribution Store with the Nielsen Measurement disclosure below:<br />
<br />
This app features Nielsen's proprietary measurement software which will allow you to contribute to market research, like Nielsen's TV Ratings. Please see http://www.nielsen.com/digitalprivacy for more information.<br />
</blockquote><br />
<br />
== Testing ==<br />
Before providing an app build to Nielsen for testing, it is important to run validation checks once you have enabled debug logging.<br />
<br />
=== Payload Validation ===<br />
<br />
Ensure that all of the required payload data is populating while testing several videos. The following areas are critical to measurement:<br />
*devInfo<br />
*Asset metadata for both content, and ads<br />
*Events<br />
*Opt-Out status<br />
<br />
=== Player Events ===<br />
Review event calls:<br />
<br />
==== playhead ====<br />
*Playhead position updates every 10 seconds starting at position '0' for each new asset.<br />
*Final playhead position is sent on content, or ad before switching between assets.<br />
*Content metadata remains constant throughout an episode, or clip play.<br />
*Ad metadata is populated appropriately for each individual ad.<br />
*Playhead position update resumes for content after an ad break, and resets to 0 for each individual ad.<br />
*For scrubbing, last current position should be sent while scrubbing occurs, and the new position should also be sent where the user scrubs to.<br />
*Exiting a stream early should execute the last current position in a playhead update to receive accurate duration.<br />
*Upon pause, the current position should be sent, and playhead updates should stop incrementing until resume play occurs.<br />
<br />
==== complete ====<br />
*Check that the complete event executes upon content complete after the final playhead update is sent<br />
*Do not execute the complete event for ads<br />
<br />
==== delete ====<br />
*Check to see that the delete event occurs upon app exit, if the platform has the necessary exit callback events.<br />
<br />
==== GET Request Format ====<br />
*Ensure that the event payloads are formatted in JSON<br />
*Check to see that each of the Cloud API GET requests are properly encoded<br />
<br />
==== HTTP Response ====<br />
*Make sure that each of the Cloud API Get requests are received by the Nielsen Cloud API properly through use of the HTTP Response Code outputs enabled in console.<br />
<br />
==== Opt-Out ====<br />
*Test the "uoo" key gets populated accurately for both Opt-In and Opt-Out selections by validating the Cloud API events called after the user Opt-Out/Opt-In selection.<br />
*Test that the devId field is populated with a blank value if a user has elected to Opt-Out. For example: "devId": "",<br />
*If the device supports "Limit Ad Tracking" or has device "Opt-Out" settings, test that uoo=true, and that devId is set to a blank value if enabled in the device settings.<br />
<br />
== Go Live ==<br />
After your integration has been certified, you will need to: Change Endpoint and Disable Logging.<br />
<br />
'''Change Endpoint:''' You will need to update to the production endpoint:<br />
<br />
*Testing: <code>https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/</code><br />
*Production: <code>https://cloudapi.imrworldwide.com/nmapi/v2/</code><br />
<br />
Your production URL structure should now be:<br />
<code>https://cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=[payload]</code><br />
<br />
'''Disable Logging:''' You can now disable debug logging</div>AlexGutierrezhttps://engineeringportal.nielsen.com//w/index.php?title=DCR_Video_%26_Static_Mobile_Cloud_API&diff=3631DCR Video & Static Mobile Cloud API2019-05-03T16:53:28Z<p>AlexGutierrez: </p>
<hr />
<div>{{Breadcrumb|}} {{Breadcrumb|Digital}} {{Breadcrumb|DCR & DTVR}} {{CurrentBreadcrumb}}<br />
[[Category:Digital]]<br />
<br />
This guide shows you how to integrate the Nielsen Cloud API to enable Digital Content Ratings (DCR) measurement on your mobile apps.<br />
*For Roku Apps, please see [[DCR Video & Static Roku Cloud API]]<br />
*For OTT Devices, please see [[DCR Video & Static Cloud API]]<br />
<br />
==Prerequisites==<br />
To get started, you will need a Nielsen App ID. The App ID is a unique ID assigned to your app. This will be provided to you upon starting the integration.<br />
<br />
<syntaxhighlight lang="javascript">XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX</syntaxhighlight><br />
<br />
==Integration==<br />
We will cover the steps for constructing the Cloud API Calls.<br />
<br />
===Request Overview===<br />
<br />
====URL Structure====<br />
<br />
The Cloud API Calls are HTTP GET Requests with the URL structure:<br />
<br />
<syntaxhighlight lang="javascript">[endpoint]/[appid]/[sessionID]/a?b=[payload]</syntaxhighlight><br />
<br />
The URL includes the following components:<br />
<br />
*<code>[endpoint]</code>: location of data collection environment<br />
*<code>[appid]</code>: provided App ID<br />
*<code>[sessionID]</code>: unique value for each user session<br />
*<code>[payload]</code>: metadata and events<br />
<br />
====Endpoint====<br />
<br />
There are endpoints for testing and production:<br />
<br />
*Testing: <code>https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/</code><br />
*Production: <code>https://cloudapi.imrworldwide.com/nmapi/v2/</code><br />
<br />
During testing, all calls should be pointed to the testing endpoint. We will review the update to the production endpoint during the Go Live section of this guide.<br />
<br />
====URL Example====<br />
As you move through the integration steps, you can reference the below URL structure with the expanded payload:<br />
<br />
<syntaxhighlight lang="javascript"><br />
https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=<br />
{<br />
"devInfo": [deviceInfo],<br />
"metadata": {<br />
"static": [static_metadata],<br />
"content": [content_metadata],<br />
"ad": [ad metadata]<br />
},<br />
"event": [event],<br />
"position": [playhead_position],<br />
"type": [asset type],<br />
"utc": [UTC]<br />
}<br />
</syntaxhighlight><br />
<br />
===Create Session ID===<br />
A unique Session ID must be created upon app launch and provided in the URL. This will allow measurement to occur for the entire duration that a user is within the app.<br />
<br />
A Session ID needs to be completely unique so it is recommended to use a version 4 UUID or another method of your choosing to guarantee there are no repeats.<br />
<br />
Upon exiting the app, the session will need to be terminated using the delete event. Sessions will automatically expire after 30 minutes of cloud inactivity.<br />
<br />
===Define URL Structure===<br />
Define the URL structure using your provided <code>[appid]</code> and a unique <code>[sessionID]</code>.<br />
<br />
<syntaxhighlight lang="javascript">https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=[payload]</syntaxhighlight><br />
<br />
===Configure Payload===<br />
<br />
All Cloud API requests must contain the following payload data:<br />
<br />
*''devInfo'': device and app info<br />
*''metadata'': asset metadata<br />
*''event metadata'': type of event<br />
<br />
The payload can be passed through key-values using the Nielsen reserved keys. The specific keys and descriptions are highlighted in the tables included in this section.<br />
<br />
'''Payload Example'''<br />
<br />
The example below should be referenced when following the steps for configuring the request payload.<br />
<br />
<syntaxhighlight lang="javascript"><br />
// 3.1 Configure Payload: devInfo <br />
payload = {<br />
"devInfo": {<br />
"devId": "AD-ID", <br />
"apn": "AppName",<br />
"apv": "1.0",<br />
"uoo": "false"<br />
},<br />
<br />
// 3.2 Configure Payload: metadata<br />
"metadata": {<br />
"static": {}, // object for measuring static content<br />
"content": { // object for measuring video content<br />
"type": "content", // "content" for video<br />
"assetid": "VIDEO-ID123", // unique ID for video<br />
"isfullepisode": "y", // full episode flag<br />
"program": "Program Name", // program name<br />
"title": "Episode Title S3 - EP1", // episode name<br />
"length": "1800", // content duration in seconds<br />
"segB": "Custom Segment B", // custom segment<br />
"segC": "Custom Segment C", // custom segment<br />
"crossId1": "Standard Episode ID", // episode ID<br />
"crossId2": "Content Originator ID", // content orginator (required for distributors)<br />
"airdate": "20161013 20:00:00", // airdate<br />
"adloadtype": "2" //ad load flag<br />
"hasAds": "1", // content contains ads = 1 / no ads = 0<br />
"progen": "CV" // program genre abbreviation<br />
},<br />
"ad": {<br />
"type": "preroll", // type of ad<br />
"assetid": "AD-ID123" // unique ID for ad<br />
}<br />
},<br />
<br />
// 3.3 Configure Payload: events<br />
"event": "playhead", //event name<br />
"position": "300", // position in seconds<br />
"type": "content", //"content" or "ad"<br />
"utc": "1456448742000" //unix timestamp in milliseconds <br />
}<br />
</syntaxhighlight><br />
<br />
=====Configure Payload: devInfo=====<br />
An object <code>"devInfo"</code> will need to be created to capture App and Device information.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| devId || unique ID to identify user (e.g. Advertising ID, Roku Device ID) || custom || Yes<br />
|-<br />
| apn || app name || custom || Yes<br />
|-<br />
| apv || app build version || custom || Yes<br />
|-<br />
| uoo || device opt-out status || <code>"true"</code> or <code>"false"</code> || Yes<br />
|-<br />
|}<br />
<br />
On mobile devices you will need to pass the IDFA/Ad ID as part of the devId parameter. Please see the section below regarding Opt Out and the IDFA/Ad ID.<br />
<br />
===== Accessing the IDFA on iOS =====<br />
<br />
Accessing the ID For Advertisers (IDFA) uses OS level API calls. In order to do this, you must first import the following header file:<br />
<syntaxhighlight lang="objective-c">#import <AdSupport/ASIdentifierManager.h></syntaxhighlight><br />
<br />
Then to receive the IDFA as a NSString, use similar code to the following:<br />
<syntaxhighlight lang="objective-c">NSString *idfaString = [[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString];</syntaxhighlight><br />
You can read more about ASIdentifierManager on Apple's [https://developer.apple.com/reference/adsupport/asidentifiermanager| developer website].<br />
<br />
===== Accessing the Ad ID on Android =====<br />
<br />
Accessing the Google Ad ID uses OS level API calls. In order to do this, you must first import Google Play Services. Then to receive the Ad ID, use the AdvertisingIdClient class as such:<br />
<syntaxhighlight lang="java">public static AdvertisingIdClient.Info getAdvertisingIdInfo (Context context)</syntaxhighlight><br />
<br />
You can read more about the AdvertisingIdClient class on Google's [https://developers.google.com/android/reference/com/google/android/gms/ads/identifier/AdvertisingIdClient| developer website].<br />
<br />
'''Example devInfo Object'''<br />
<syntaxhighlight lang="javascript"><br />
// create devInfo object<br />
"devInfo": {<br />
"devId": "AD-ID",<br />
"apn": "AppName",<br />
"apv": "1.0",<br />
"uoo": "false"<br />
},<br />
</syntaxhighlight><br />
<br />
==== 3.2 Configure Payload: metadata ====<br />
Asset metadata can be passed through <code>"metadata"</code>. There are two asset types: <code>"content"</code> for video and <code>"ad"</code> for ads. The metadata received for each asset is used for classification and reporting.<br />
<br />
You will need to set up <code>"metadata"</code> objects for <code>"content"</code> and <code>"ad"</code> with the required Nielsen keys as shown in the sample code below.<br />
<br />
===== Content Metadata =====<br />
Content metadata should remain constant throughout the entirety of an episode/clip including when ads play.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| type || type of asset || <code>"content"</code> || Yes<br />
|-<br />
| assetid || unique ID assigned to asset || custom || Yes<br />
|-<br />
| program ||name of program (25 character limit) || custom || Yes<br />
|-<br />
| title ||name of program (25 character limit) || custom || Yes<br />
|-<br />
| length || length of content in seconds || <code>seconds</code> (0 for live stream) || Yes<br />
|-<br />
| segB || custom segment B || custom || No<br />
|-<br />
| segC || custom segment C || custom || No<br />
|-<br />
| airdate || the airdate in the linear TV || YYYYMMDD HH24:MI:SS || Yes<br />
|-<br />
| isfullepisode || full episode flag || <code>"y"</code>- full episode, <code>"n"</code>- non full episode || Yes<br />
|-<br />
| crossId1 || standard episode ID || custom || Yes<br />
|-<br />
| crossId2 || content originator (only required for distributors) || Nielsen || No<br />
|-<br />
| adloadtype || type of ad load:<br />
<code>"1"</code> Linear – matches TV ad load<br />
<br />
<code>"2"</code> Dynamic – Dynamic Ad Insertion (DAI)<br />
|| <code>"2"</code> - DCR measures content with dynamic ads || Yes<br />
|-<br />
| hasAds || ads indicator<br />
<code>"1"</code>: ads included<br />
<br />
<code>"0"</code>: ads not included<br />
|| <code>"1"</code> or <code>"0"</code> || Yes<br />
|-<br />
| subbrand || sub brand override || Nielsen || No<br />
|-<br />
| progen || program genre abbreviation - see [[DCR OTT Genre List]] for accepted values || <code>"CV"</code> for Comedy Variety || Yes<br />
|}<br />
<br />
<br />
'''Example Content Object'''<br />
<syntaxhighlight lang='json'>// create content object<br />
"content": {<br />
"type": "content",<br />
"assetid": "VIDEO-ID123",<br />
"isfullepisode": "y",<br />
"program": "Program Name",<br />
"title": "Episode Title S3 - EP1",<br />
"length": "1800",<br />
"segB": "Custom Segment B",<br />
"segC": "Custom Segment C",<br />
"crossId1": "Standard Episode ID",<br />
"crossId2": "Content Originator ID",<br />
"airdate": "20161013 20:00:00",<br />
"adloadtype": "2",<br />
"hasAds": "1", <br />
"subbrand": "c05",<br />
"progen": "CV"<br />
}</syntaxhighlight><br />
<br />
===== Ad Metadata =====<br />
The ad metadata should be passed for each individual ad.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| type || type of ad || <code>"preroll"</code>, <code>"midroll"</code>, or <code>"postroll"</code> || Yes<br />
|-<br />
| assetid || unique ID assigned to ad || custom || Yes<br />
|}<br />
<br />
===== Example Ad Object =====<br />
<syntaxhighlight lang="javascript"><br />
// create ad object<br />
"ad": {<br />
"type": "preroll",<br />
"assetid": "AD-ID123"<br />
}<br />
</syntaxhighlight><br />
<br />
=== Configure Payload: Events ===<br />
<br />
The last part of the payload is for enabling events so content is measured correctly when viewed. The events and required parameters are included below.<br />
<br />
==== Event Types ====<br />
<br />
The available events are:<br />
{| class="wikitable"<br />
|-<br />
! Event !! Description<br />
|-<br />
| <code>"playhead"</code> || Playhead position in seconds, must be passed as a whole number every 10 seconds. The final playhead position should be sent before an asset has changed to properly capture full duration. When content is paused, stop passing playhead position until content is resumed. On playhead scrubbing, send current playhead position, followed by the playhead position the user scrubs to. For Live streams, you may use Unix Time (in seconds) as the playhead position. Note that ad playheads must also use Unix Time if Unix Time is used for content playheads.<br />
|-<br />
| <code>"complete"</code> || The complete event must be sent when the content has completed full playback. Before calling the complete event, a final playhead update with the final position is required to be sent to receive full duration credit. For Live streams, a complete event must be sent at program boundaries.<br />
|-<br />
| <code>"delete"</code> || The delete event is optional and can be sent when the viewing session is terminated (typically on App close). A new session ID must be generated after sending a delete event. Delete should not be sent on app interruptions or foreground/background events. All creditable duration will be summarized for all asset types when delete occurs (content and ads).<br />
|}<br />
<br />
===== Event Parameters =====<br />
<br />
The following parameters need to be passed when calling events:<br />
<br />
{| class="wikitable"<br />
|-<br />
! Parameter !! Description !! Value !! Required<br />
|-<br />
| <code>"event"</code> || event type || <code>"playhead"</code>, <code>"complete"</code>, or <code>"delete"</code> || Yes<br />
|-<br />
| <code>"position"</code> || creditable position || playhead position in seconds or Unix timestamp (seconds since Jan-1-1970 UTC) for livestream || Yes<br />
|-<br />
| <code>"type"</code> || asset type || <code>"content"</code>, <code>"ad"</code> || Yes<br />
|-<br />
| <code>"utc"</code> || Unix timestamp in milliseconds. Must be passed every 10 seconds. || <code>"1472760000000"</code> || Yes<br />
|}<br />
<br />
===== Example Event =====<br />
You can call events by passing values in the required parameters:<br />
<br />
<syntaxhighlight lang="javascript"><br />
"devInfo": [deviceInfo],<br />
"metadata": {<br />
"static": [static metadata],<br />
"content": [content metadata],<br />
"ad": [ad metadata]<br />
},<br />
// Event Parameters<br />
"event": [event], // event name<br />
"position": [playheadPosition], //position in seconds<br />
"type": [asset type], // values are "content" or "ad"<br />
"utc": "1472760000000" //unix timestamp in milliseconds<br />
}<br />
</syntaxhighlight><br />
<br />
'''Note:''' The full payload including "devInfo" and "metadata" must be populated in each event request.<br />
<br />
===== Sample Event Lifecycle =====<br />
The sample event lifecycle can be used as a reference for identifying the order for calling events and values to pass.<br />
<br />
<syntaxhighlight lang="javascript"><br />
// Start of Session: session ID created when App is opened<br />
<br />
// Preroll<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
// Content<br />
Content Playhead {"event": "playhead", "position": "0", "type": "content", "utc": "1472760000000"} <br />
<br />
// Midroll<br />
Midroll Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
// Content resumes at 15 minutes<br />
Content Playhead {"event": "playhead", "position": "900", "type": "content", "utc": "1472760000000"} <br />
<br />
// Content completes at 30 minutes<br />
Complete {"event": "complete", "position": "1800", "type": "content", "utc": "1472760000000"} <br />
<br />
// Postroll<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
//End of Session: The delete event should be called when the App is exited. The values for position and type not required to be passed.<br />
Delete { "event": "delete", "position": "", "type": "", "utc": "1472760000000"} <br />
</syntaxhighlight><br />
<br />
<br />
'''Sample Event Lifecycle - Detailed Storyline'''<br />
This detailed event sequence provides additional insight for the correct events to call when handling certain playback scenarios.<br />
<syntaxhighlight lang='javascript'>// SESSION STARTS<br />
// Start of Session: session ID created when App is opened<br />
<br />
// PREROLL<br />
// Preroll Start - Start each Ad with a position of "0", resetting to '0' for each Ad, and Ad break.<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
// Preroll Stop - End each Ad with the final position of the Ad.<br />
Ad Playhead {"event": "playhead", "position": "15", "type": "ad", "utc": "1472761500000"}<br />
<br />
// CONTENT <br />
// Content Start - Start new content streams with a position of "0" incrementing the position every 10 seconds.<br />
Content Playhead {"event": "playhead", "position": "0", "type": "content", "utc": "1472761500000"} <br />
<br />
// Content Stop Before Ad Break - Send a playhead update including the current content positon before an Ad break.<br />
Content Playhead {"event": "playhead", "position": "299", "type": "content", "utc": "1472787400000"}<br />
<br />
// MIDROLL<br />
// Midroll Start - Start each Ad with a position of "0", resetting to '0' for each Ad, and Ad break.<br />
Midroll Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472787500000"} <br />
<br />
// Midroll Stop - End each Ad with the final position of the Ad.<br />
Ad Playhead {"event": "playhead", "position": "60", "type": "ad", "utc": "1472793500000"}<br />
<br />
// CONTENT<br />
// Content resumes at 5 minutes - Send playhead update with the current resumed position, and begin incrimenting the positon every 10 seconds.<br />
Content Playhead {"event": "playhead", "position": "300", "type": "content", "utc": "1472799500000"} <br />
<br />
// Content completes at 10:12 - Make sure to send in the playhead event with the final content position before sending the complete event.<br />
Final Content Playhead {"event": "playhead", "position": "612", "type": "content", "utc": "1472830700000"} <br />
<br />
Complete {"event": "complete", "position": "612", "type": "content", "utc": "1472830800000"} <br />
<br />
// POSTROLL<br />
// Postroll Start - Start each Ad with a position of "0", resetting to '0' for each Ad, and Ad break.<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472830900000"} <br />
<br />
// Postroll Stop - End each Ad with the final position of the Ad.<br />
Ad Playhead {"event": "playhead", "position": "45", "type": "ad", "utc": "1472835300000"}<br />
<br />
// SESSION ENDS<br />
<br />
//End of Session: The delete event should be called when the App is exited. The values for position and type not required to be passed.<br />
Delete { "event": "delete", "position": "", "type": "", "utc": "1472835400000"} </syntaxhighlight><br />
<br />
<br />
=====Handling Playhead=====<br />
Calling <code>"playhead"</code> is critical for accurate duration crediting. You can reference the below guidance to determine the correct playhead position to pass depending on the playback scenario.<br />
<br />
'''Playhead: General'''<br />
* Playhead position must start at 0 for each new asset, and be passed at least every 10 seconds.<br />
* Final postion must be sent at the end of content or an ad<br />
<br />
'''Playhead: Ads'''<br />
* The final position must be sent when switching from content to ad, or ad to content.<br />
* Each ad playhead position should be 0 at ad start.<br />
* For Ad Pods, playhead must be called, and reset to 0 for each individual ad. <br />
* The last content position before an Ad should be sent before switching to Ads.<br />
* When content has resumed following an ad break, the playhead position update must continue where the previous content segment left off.<br />
<br />
'''Playhead: User Actions'''<br />
* Upon user scrubbing, the current position must be sent before a user scrubs, and the new position should be sent where the user lands, and begin sending in the 10 second updates thereafter.<br />
* On pause, send the current position and then discontinue sending playhead event updates.<br />
* If a user exits a stream early, the last current position must be sent in a playhead update to receive accurate duration.<br />
<br />
===== Interruption Scenarios =====<br />
<br />
As part of configuring events, you will need to handle all possible interruption scenarios such as:<br />
<br />
*Pause / Play<br />
*Network Loss (Wi-Fi / Airplane Mode / Cellular)<br />
*Wi-Fi OFF / ON<br />
*Call Interrupt (SIM or Third party Skype / Hangout call)<br />
*Alarm Interrupt<br />
*Content Buffering<br />
*Device Lock / Unlock (Video players only, not for Audio players)<br />
*App going Background / Foreground (Video players only, not for Audio players)<br />
*App Crash or Exit<br />
*Channel / Station Change Scenario<br />
*Unplugging of headphone<br />
<br />
When playback is temporarily interrupted (e.g. pause, content buffering), the app needs to send the last known playhead position.<br />
<br />
*If an app is sent to background for more than 5 minutes, please create a new session ID. Otherwise, use the same session ID as before.<br />
*If loss of Internet occurs, please queue the API calls that would have been made. Once Internet connectivity is regained, please spool off the API calls in order of first generated (Note: if doing so, please use the UTC time in milliseconds)<br />
<br />
When playback is permanently interrupted, the app needs to send delete immediately.<br />
*If an app crashes, please create a new session ID. No delete call will be necessary as the previous session will timeout.<br />
<br />
Once playback resumes after delete occurs, a new session will need to be created with a unique session ID. All of the required metadata and events will need to be sent.<br />
<br />
'''Note:''' The session will automatically timeout after 30 minutes of inactivity.<br />
<br />
=== Example Request ===<br />
<br />
Now that we walked through the Cloud API integration steps, your requests should have the following components: Session ID, App ID, and Payload. You can reference the example below when your reviewing your integration.<br />
<br />
<syntaxhighlight lang="javascript"><br />
// 1. Create Session ID<br />
sessionID = dfc7dc6a-66a7-4705-9fba-adaaf7e3d5e0 // Example sessionID created using a UUID Generator<br />
<br />
// 2. Define URL Structure with App ID and Session ID<br />
sessionURL = https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=<br />
<br />
// 3. Configure Payload<br />
// 3.1 Configure Payload: devInfo <br />
payload = {<br />
"devInfo": {<br />
"devId": "AD-ID", <br />
"apn": "AppName",<br />
"apv": "1.0",<br />
"uoo": "false"<br />
},<br />
<br />
// 3.2 Configure Payload: metadata<br />
"metadata": {<br />
"static": {}, // object for measuring static content<br />
"content": { // object for measuring video content<br />
"type": "content", // "content" for video<br />
"assetid": "VIDEO-ID123", // unique ID for video<br />
"isfullepisode": "y", // full episode flag<br />
"program": "Program Name", // program name<br />
"title": "Episode Title S3 - EP1", // episode name<br />
"length": "1800", // content duration in seconds<br />
"segB": "Custom Segment B", // custom segment<br />
"segC": "Custom Segment C", // custom segment<br />
"crossId1": "Standard Episode ID", // episode ID<br />
"crossId2": "Content Originator ID", // content orginator (required for distributors)<br />
"airdate": "20161013 20:00:00", // airdate<br />
"adloadtype": "2", // ad load flag<br />
"hasAds": "1", // content contains ads = 1 / no ads = 0<br />
"progen": "CV" // program genre abbreviation<br />
},<br />
"ad": {<br />
"type": "preroll", // type of ad<br />
"assetid": "AD-ID123" // unique ID for ad<br />
}<br />
},<br />
<br />
// 3.3 Configure Payload: events<br />
"event": "playhead", //event name<br />
"position": "300", // position in seconds<br />
"type": "content", //"content" or "ad"<br />
"utc": "1456448742000" //unix timestamp in milliseconds <br />
}<br />
<br />
// Append payload to URL<br />
var image = new Image()<br />
image.onerror = function() {<br />
// wait and send again<br />
}<br />
(new Image).src = sessionURL+encodeURI(JSON.stringify(payload));<br />
</syntaxhighlight><br />
<br />
==== Enable Debug Logging ====<br />
<br />
Now that you have set up the Cloud API requests, you can enable debug logging to validate your integration. Enabling debug logging is required for Nielsen certification.<br />
<br />
==== GET Request ====<br />
<br />
Display GET Request to console using a name to identify each event (e.g. playhead).<br />
<br />
<syntaxhighlight lang="javascript"><br />
console.log("Event", image); <br />
</syntaxhighlight><br />
<br />
==== Payload ====<br />
<br />
Output payload to identify required metadata and events.<br />
<br />
<syntaxhighlight lang="javascript"><br />
console.log("Event Payload", payload); <br />
</syntaxhighlight><br />
<br />
==== HTTP Response Code ====<br />
<br />
Confirm request was completed by viewing HTTP response code.<br />
<br />
<syntaxhighlight lang="javascript"><br />
code = msg.GetResponseCode();<br />
console.log("Response Code", code); <br />
</syntaxhighlight><br />
<br />
You can reference the HTTP Response Code table when reviewing your requests:<br />
<br />
{| class="wikitable"<br />
|-<br />
! Status Code !! Status Text !! Description<br />
|-<br />
| <code>200</code> || OK || request received<br />
|-<br />
| <code>403</code> || Forbidden || invalid App ID<br />
|-<br />
| <code>404</code> || Not Found || JSON issue<br />
|}<br />
<br />
== Opt-Out ==<br />
Your app must provide a means for the user to Opt-Out, or Opt-In to Nielsen Measurement. The Opt-Out requirement can be fulfilled by creating an Opt-Out/Opt-In button, toggle switch, or slider within the app "Settings", or "About" section to allow the user selection. If you have the ability to render text within the Opt-Out screen, it is recommended to add the Privacy Information included below.<br />
<br />
[[File:Nielsen Opt-Out.png|link=]]<br />
<br />
You will need to store the User Opt-Out (uoo) status, so that it can be retrieved and populated in the <code>"devInfo"</code> metadata which will be sent in every Cloud API event (playhead, complete, & delete). <br />
<br />
*In addition, you will need to ensure that the devId field is a blank value if a user has elected to Opt-Out<br />
<br />
*If the device offers "Limit Ad Tracking" or "Opt-Out" device settings, you must set uoo=true, and also ensure that the devId is set to a blank value if the user elects to enable this setting.<br />
<br />
{| class="wikitable"<br />
|-<br />
! uoo Key !! Description !! Values<br />
|-<br />
| uoo || Device is Opted-In to Nielsen Measurement (Recommended Default Setting) || <code>"false"</code><br />
|-<br />
| uoo || Device is Opted-Out of Nielsen Measurement || <code>"true"</code><br />
|}<br />
<br />
===== devInfo Opt-In JSON Payload Example =====<br />
<br />
<syntaxhighlight lang="javascript"><br />
"devInfo": {<br />
"apn": "Roku Sample App",<br />
"apv": "1",<br />
"devId": "7be25cf9-8c40-5cc2-871e-19bf41940288",<br />
"uoo": "false"<br />
},<br />
</syntaxhighlight><br />
<br />
===== devInfo Opt-Out JSON Payload Example =====<br />
<br />
<syntaxhighlight lang="javascript"><br />
"devInfo": {<br />
"apn": "Roku Sample App",<br />
"apv": "1",<br />
"devId": "", //devId must be blank when a user elects to Opt-Out.<br />
"uoo": "true"<br />
},<br />
</syntaxhighlight><br />
<br />
===== Privacy Information Template To Include In Opt-Out Screen =====<br />
<br />
<blockquote><br />
'''''ABOUT NIELSEN MEASUREMENT'''''<br />
<br />
Television and the way we watch it have come a long way since Nielsen began measuring TV audiences in 1950. Today, the ability to watch our favorite shows at any time and on multiple devices amplifies the need for exceptionally adept and flexible audience measurement capabilities.<br />
<br />
Consumers are changing with the times, and the same goes for us. As technology continues to evolve and media companies try new ways to attract viewers, understanding what consumers are watching — and what they're watching on — is more important than ever. Today, viewing video is a personal and mobile experience — anytime and anywhere. Our capabilities provide relevant metrics that are necessary to inform successful marketing and programming and drive continued growth. As a global information and measurement leader, we are committed to protecting the privacy and security of the data we collect, process and use. While our digital measurement products are not used to identify you in any way, they help us and our clients measure and analyze how consumers engage with media across online, mobile and emerging technologies, and offer insights into consumer behavior.<br />
<br />
'''''YOUR CHOICES'''''<br />
<br />
Nielsen believes that you should have a choice about whether to contribute to our research and insights. To opt out, or opt into Nielsen measurement please make the appropriate selection within the app. If you have this app on more than one device, you will need to opt out of this app on each device. To learn more about our digital measurement products and your choices in regard to them, please visit http://www.nielsen.com/digitalprivacy.<br />
<br />
'''''Disclosure Template'''''<br />
<br />
Additionally, you must update the App description information from the App Distribution Store with the Nielsen Measurement disclosure below:<br />
<br />
This app features Nielsen's proprietary measurement software which will allow you to contribute to market research, like Nielsen's TV Ratings. Please see http://www.nielsen.com/digitalprivacy for more information.<br />
</blockquote><br />
<br />
== Testing ==<br />
Before providing an app build to Nielsen for testing, it is important to run validation checks once you have enabled debug logging.<br />
<br />
=== Payload Validation ===<br />
<br />
Ensure that all of the required payload data is populating while testing several videos. The following areas are critical to measurement:<br />
*devInfo<br />
*Asset metadata for both content, and ads<br />
*Events<br />
*Opt-Out status<br />
<br />
=== Player Events ===<br />
Review event calls:<br />
<br />
==== playhead ====<br />
*Playhead position updates every 10 seconds starting at position '0' for each new asset.<br />
*Final playhead position is sent on content, or ad before switching between assets.<br />
*Content metadata remains constant throughout an episode, or clip play.<br />
*Ad metadata is populated appropriately for each individual ad.<br />
*Playhead position update resumes for content after an ad break, and resets to 0 for each individual ad.<br />
*For scrubbing, last current position should be sent while scrubbing occurs, and the new position should also be sent where the user scrubs to.<br />
*Exiting a stream early should execute the last current position in a playhead update to receive accurate duration.<br />
*Upon pause, the current position should be sent, and playhead updates should stop incrementing until resume play occurs.<br />
<br />
==== complete ====<br />
*Check that the complete event executes upon content complete after the final playhead update is sent<br />
*Do not execute the complete event for ads<br />
<br />
==== delete ====<br />
*Check to see that the delete event occurs upon app exit<br />
<br />
==== GET Request Format ====<br />
*Ensure that the event payloads are formatted in JSON<br />
*Check to see that each of the Cloud API GET requests are properly encoded<br />
<br />
==== HTTP Response ====<br />
*Make sure that each of the Cloud API Get requests are received by the Nielsen Cloud API properly through use of the HTTP Response Code outputs enabled in console.<br />
<br />
==== Opt-Out ====<br />
*Test the "uoo" key gets populated accurately for both Opt-In and Opt-Out selections by validating the Cloud API events called after the user Opt-Out/Opt-In selection.<br />
*Test that the devId field is populated with a blank value if a user has elected to Opt-Out. For example: "devId": "",<br />
*If the device supports "Limit Ad Tracking" or has device "Opt-Out" settings, test that uoo=true, and that devId is set to a blank value if enabled in the device settings.<br />
<br />
== Go Live ==<br />
After your integration has been certified, you will need to: Change Endpoint and Disable Logging.<br />
<br />
'''Change Endpoint:''' You will need to update to the production endpoint:<br />
<br />
*Testing: <code>https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/</code><br />
*Production: <code>https://cloudapi.imrworldwide.com/nmapi/v2/</code><br />
<br />
Your production URL structure should now be:<br />
<code>https://cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=[payload]</code><br />
<br />
'''Disable Logging:''' You can now disable debug logging</div>AlexGutierrezhttps://engineeringportal.nielsen.com//w/index.php?title=DCR_Video_%26_Static_Roku_Cloud_API&diff=3627DCR Video & Static Roku Cloud API2019-05-03T16:44:19Z<p>AlexGutierrez: </p>
<hr />
<div>{{Breadcrumb|}} {{Breadcrumb|Digital}} {{Breadcrumb|DCR & DTVR}} {{CurrentBreadcrumb}}<br />
[[Category:Digital]]<br />
<br />
This guide shows you how to integrate the Nielsen Cloud API to enable Digital Content Ratings (DCR) measurement on your Roku Apps.<br />
*For other OTT Apps, please see [[DCR Video & Static Cloud API]]<br />
*For Mobile Apps, please see [[DCR Video & Static Mobile Cloud API]]<br />
<br />
==Prerequisites==<br />
To get started, you will need a Nielsen App ID. The App ID is a unique ID assigned to your app. This will be provided to you upon starting the integration.<br />
<br />
<syntaxhighlight lang="javascript">XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX</syntaxhighlight><br />
<br />
==Integration==<br />
We will cover the steps for constructing the Cloud API Calls.<br />
<br />
===Request Overview===<br />
<br />
====URL Structure====<br />
<br />
The Cloud API Calls are HTTP GET Requests with the URL structure:<br />
<br />
<syntaxhighlight lang="javascript">[endpoint]/[appid]/[sessionID]/a?b=[payload]</syntaxhighlight><br />
<br />
The URL includes the following components:<br />
<br />
*<code>[endpoint]</code>: location of data collection environment<br />
*<code>[appid]</code>: provided App ID<br />
*<code>[sessionID]</code>: unique value for each user session<br />
*<code>[payload]</code>: metadata and events<br />
<br />
====Endpoint====<br />
<br />
There are endpoints for testing and production:<br />
<br />
*Testing: <code>https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/</code><br />
*Production: <code>https://cloudapi.imrworldwide.com/nmapi/v2/</code><br />
<br />
During testing, all calls should be pointed to the testing endpoint. Once your application has been certified, the URL should be updated to point to the Cloud API production endpoint.<br />
<br />
====URL Example====<br />
As you move through the integration steps, you can reference the below URL structure with the expanded payload:<br />
<br />
<syntaxhighlight lang="javascript"><br />
https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=<br />
{<br />
"devInfo": [deviceInfo],<br />
"metadata": {<br />
"static": [static_metadata],<br />
"content": [content_metadata],<br />
"ad": [ad metadata]<br />
},<br />
"event": [event],<br />
"position": [playhead_position],<br />
"type": [asset type],<br />
"utc": [UTC]<br />
}<br />
</syntaxhighlight><br />
<br />
===Create Session ID===<br />
A unique Session ID must be created upon app launch and provided in the URL. This will allow measurement to occur for the entire duration that a user is within the app.<br />
<br />
The session ID must be passed with every request and must remain consistent throughout each individual session.<br />
<br />
A Session ID needs to be completely unique so it is recommended to use a version 4 UUID or another method of your choosing to guarantee there are no repeats.<br />
<br />
Upon exiting the app, the session will need to be terminated using the delete event. Sessions will automatically expire after 30 minutes of cloud inactivity.<br />
<br />
===Define URL Structure===<br />
Define the URL structure using your provided <code>[appid]</code> and a unique <code>[sessionID]</code>.<br />
<br />
<syntaxhighlight lang="javascript">https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=[payload]</syntaxhighlight><br />
<br />
===Configure Payload===<br />
<br />
All Cloud API requests must contain the following payload data:<br />
<br />
*''devInfo'': device and app info<br />
*''metadata'': asset metadata<br />
*''event metadata'': type of event<br />
<br />
The payload can be passed through key-values using the Nielsen reserved keys. The specific keys and descriptions are highlighted in the tables included in this section.<br />
<br />
'''Payload Example'''<br />
<br />
The example below should be referenced when following the steps for configuring the request payload.<br />
<br />
<syntaxhighlight lang="javascript"><br />
// 3.1 Configure Payload: devInfo <br />
payload = {<br />
"devInfo": {<br />
"apn": "AppName",<br />
"encdata": "encdata=v1.key.RokuAES256CBC00.iv3x2EV0BpHH9AbZK%2FnBWWRhZbj7pD%3D...",<br />
"apv": "1.0",<br />
"uoo": "false"<br />
},<br />
<br />
// 3.2 Configure Payload: metadata<br />
"metadata": {<br />
"static": {}, // object for measuring static content<br />
"content": { // object for measuring video content<br />
"type": "content", // "content" for video<br />
"assetid": "VIDEO-ID123", // unique ID for video<br />
"isfullepisode": "y", // full episode flag<br />
"program": "Program Name", // program name<br />
"title": "Episode Title S3 - EP1", // episode name<br />
"length": "1800", // content duration in seconds<br />
"segB": "Custom Segment B", // custom segment<br />
"segC": "Custom Segment C", // custom segment<br />
"crossId1": "Standard Episode ID", // episode ID<br />
"crossId2": "Content Originator ID", // content orginator (required for distributors)<br />
"airdate": "20161013 20:00:00", // airdate<br />
"adloadtype": "2", //ad load flag<br />
"hasAds": "1", // content contains ads = 1 / no ads = 0<br />
"progen": "CV" // program genre abbreviation<br />
},<br />
"ad": {<br />
"type": "preroll", // type of ad<br />
"assetid": "AD-ID123" // unique ID for ad<br />
}<br />
},<br />
<br />
// 3.3 Configure Payload: events<br />
"event": "playhead", //event name<br />
"position": "300", // position in seconds<br />
"type": "content", //"content" or "ad"<br />
"utc": "1456448742000" //unix timestamp in milliseconds <br />
}<br />
</syntaxhighlight><br />
<br />
=====Configure Payload: devInfo=====<br />
An object <code>"devInfo"</code> will need to be created to capture App and Device information.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| encdata || Nielsen - Roku ID for Advertisers || N-RIDA Payload || Yes<br />
|-<br />
| apn || app name || custom || Yes<br />
|-<br />
| apv || app build version || custom || Yes<br />
|-<br />
| uoo || device opt-out status || <code>"true"</code> or <code>"false"</code> || Yes<br />
|-<br />
| Example || Example || Example || Yes<br />
|}<br />
<br />
'''Example devInfo Object'''<br />
<syntaxhighlight lang="javascript"><br />
// create devInfo object<br />
"devInfo": {<br />
"encdata": "encdata=v1.key.RokuAES256CB...",<br />
"apn": "AppName",<br />
"apv": "1.0",<br />
"uoo": "false"<br />
},<br />
</syntaxhighlight><br />
<br />
===== Encdata (N-RIDA): Nielsen - Roku Identifier for Advertisers =====<br />
The N-RIDA (Nielsen-Roku ID for Advertisers) is a proprietary advertising identifier that ties back to an individual Roku device, and allows Nielsen to attribute audience metrics. The N-RIDA is provided by Roku, and is pulled through the Roku <code>[https://sdkdocs.roku.com/display/sdkdoc/Integrating+the+Roku+Advertising+Framework#IntegratingtheRokuAdvertisingFramework-getNielsenContentData()asString AdIface.GetNielsenContentData()]</code> API.<br />
<br />
The N-RIDA payload should be requested once per user session and populated in the <code>"encdata"</code> Nielsen key within the <code>"devInfo"</code> payload. Each Cloud API request throughout a user session should include the same N-RIDA payload. The N-RIDA should be established when a user launches the app, and it should be maintained until the user exits. A new N-RIDA payload should be requested when the user re-enters the app.<br />
<br />
'''Prerequisites For Using The AdIface.GetNielsenContentData() Roku API'''<br />
<br />
* Roku Ad Framework (RAF) must be implemented:<br />
** <code>bs_libs_required=roku_ads_lib</code> must be included in the Roku manifest file.<br />
** <code>Library "Roku_Ads.brs"</code> should be the first entry in your Roku main.brs file.<br />
** <code>adIface = Roku_Ads()</code> allows you to access the Roku Ad Framework interface and must be called before utilizing RAF API's such as <code>AdIface.GetNielsenContentData()</code><br />
* For additional details for integrating the Roku Ad Framework (RAF) please refer to the link provided below:<br />
** https://sdkdocs.roku.com/display/sdkdoc/Integrating+the+Roku+Advertising+Framework<br />
<br />
===== Request N-RIDA for the devInfo Payload encdata parameter using AdIface.GetNielsenContentData() =====<br />
<br />
'''Example Code For Configuring Encdata'''<br />
<syntaxhighlight lang="java">// create a variable as a placeholder for the N-RIDA payload<br />
n_Rida=AdIface.GetNielsenContentData()<br />
<br />
// populate the encdata key in devInfo with the AdIface.GetNielsenContentData() retrun<br />
"devInfo": {<br />
"apn": "AppName",<br />
"apv": "1.0",<br />
"encdata": "n_Rida", //insert the AdIface.GetNielsenContentData() return within encdata<br />
"uoo": "false"<br />
}</syntaxhighlight><br />
<br />
<blockquote>'''Note''': When pulled and populated into the encdata key, the <code>AdIface.GetNielsenContentData()</code> return will be formatted similarly to the example below, and will include "encdata=" within the return along with the N-RIDA key.</blockquote><br />
<syntaxhighlight lang="json">"encdata":"encdata=v1.key.RokuAES256CBC00.iv.HSh88AM%2BG%2Fx57TZ%2BW5j5Fw%3D%3D..."</syntaxhighlight><br />
<br />
==== 3.2 Configure Payload: metadata ====<br />
Asset metadata can be passed through <code>"metadata"</code>. There are two asset types: <code>"content"</code> for video and <code>"ad"</code> for ads. The metadata received for each asset is used for classification and reporting.<br />
<br />
You will need to set up <code>"metadata"</code> objects for <code>"content"</code> and <code>"ad"</code> with the required Nielsen keys as shown in the sample code below.<br />
<br />
===== Content Metadata =====<br />
Content metadata should remain constant throughout the entirety of an episode/clip including when ads play.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| type || type of asset || <code>"content"</code> || Yes<br />
|-<br />
| assetid || unique ID assigned to asset || custom || Yes<br />
|-<br />
| program ||name of program (25 character limit) || custom || Yes<br />
|-<br />
| title ||name of program (25 character limit) || custom || Yes<br />
|-<br />
| length || length of content in seconds || <code>seconds</code> (0 for live stream) || Yes<br />
|-<br />
| segB || custom segment B || custom || <br />
|-<br />
| segC || custom segment C || custom || <br />
|-<br />
| airdate || the airdate in the linear TV || YYYYMMDD HH24:MI:SS || Yes<br />
|-<br />
| isfullepisode || full episode flag || <code>"y"</code>- full episode, <code>"n"</code>- non full episode || Yes<br />
|-<br />
| crossId1 || standard episode ID || custom || Yes<br />
|-<br />
| crossId2 || content originator (only required for distributors) || Nielsen ||<br />
|-<br />
| adloadtype || type of ad load:<br />
<code>"1"</code> Linear – matches TV ad load<br />
<br />
<code>"2"</code> Dynamic – Dynamic Ad Insertion (DAI)<br />
|| <code>"2"</code> - DCR measures content with dynamic ads || Yes<br />
|-<br />
| hasAds || ads indicator<br />
<code>"1"</code>: ads included<br />
<br />
<code>"0"</code>: ads not included<br />
|| <code>"1"</code> or <code>"0"</code> || Yes<br />
|-<br />
| progen || program genre abbreviation - see [[DCR OTT Genre List]] for accepted values || <code>"CV"</code> for Comedy Variety || Yes<br />
|}<br />
<br />
<br />
'''Example Content Object'''<br />
<syntaxhighlight lang='json'>// create content object<br />
"content": {<br />
"type": "content",<br />
"assetid": "VIDEO-ID123",<br />
"isfullepisode": "y",<br />
"program": "Program Name",<br />
"title": "Episode Title S3 - EP1",<br />
"length": "1800",<br />
"segB": "Custom Segment B",<br />
"segC": "Custom Segment C",<br />
"crossId1": "Standard Episode ID",<br />
"crossId2": "Content Originator ID",<br />
"airdate": "20161013 20:00:00",<br />
"adloadtype": "2",<br />
"hasAds": "1", <br />
"progen": "CV"<br />
}</syntaxhighlight><br />
<br />
===== Ad Metadata =====<br />
The ad metadata should be passed for each individual ad.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| type || type of ad || <code>"preroll"</code>, <code>"midroll"</code>, or <code>"postroll"</code> || Yes<br />
|-<br />
| assetid || unique ID assigned to ad || custom || Yes<br />
|}<br />
<br />
===== Example Ad Object =====<br />
<syntaxhighlight lang="javascript"><br />
// create ad object<br />
"ad": {<br />
"type": "preroll",<br />
"assetid": "AD-ID123"<br />
}<br />
</syntaxhighlight><br />
<br />
=== Configure Payload: Events ===<br />
<br />
The last part of the payload is for enabling events so content is measured correctly when viewed. The events and required parameters are included below.<br />
<br />
==== Event Types ====<br />
<br />
The available events are:<br />
{| class="wikitable"<br />
|-<br />
! Event !! Description<br />
|-<br />
| <code>"playhead"</code> || Playhead position in seconds, must be passed as a whole number every 10 seconds. The final playhead position should be sent before an asset has changed to properly capture full duration. When content is paused, stop passing playhead position until content is resumed. On playhead scrubbing, send current playhead position, followed by the playhead position the user scrubs to. For Live streams, you may use Unix Time (in seconds) as the playhead position. Note that ad playheads must also use Unix Time if Unix Time is used for content playheads.<br />
|-<br />
| <code>"complete"</code> || The complete event must be sent when the content has completed full playback. Before calling the complete event, a final playhead update with the final position is required to be sent to receive full duration credit. For Live streams, a complete event must be sent at program boundaries.<br />
|-<br />
| <code>"delete"</code> || The delete event is optional and can be sent when the viewing session is terminated (typically on App close). A new session ID must be generated after sending a delete event. Delete should not be sent on app interruptions or foreground/background events. All creditable duration will be summarized for all asset types when delete occurs (content and ads).<br />
|}<br />
<br />
===== Event Parameters =====<br />
<br />
The following parameters need to be passed when calling events:<br />
<br />
{| class="wikitable"<br />
|-<br />
! Parameter !! Description !! Value !! Required<br />
|-<br />
| <code>"event"</code> || event type || <code>"playhead"</code>, <code>"complete"</code>, or <code>"delete"</code> || Yes<br />
|-<br />
| <code>"position"</code> || creditable position || playhead position in seconds or Unix timestamp (seconds since Jan-1-1970 UTC) for livestream || Yes<br />
|-<br />
| <code>"type"</code> || asset type || <code>"content"</code>, <code>"ad"</code> || Yes<br />
|-<br />
| <code>"utc"</code> || Unix timestamp in milliseconds. Must be passed every 10 seconds. || <code>"1472760000000"</code> || Yes<br />
|}<br />
<br />
===== Example Event =====<br />
You can call events by passing values in the required parameters:<br />
<br />
<syntaxhighlight lang="javascript"><br />
"devInfo": [deviceInfo],<br />
"metadata": {<br />
"static": [static metadata],<br />
"content": [content metadata],<br />
"ad": [ad metadata]<br />
},<br />
// Event Parameters<br />
"event": [event], // event name<br />
"position": [playheadPosition], //position in seconds<br />
"type": [asset type], // values are "content" or "ad"<br />
"utc": "1472760000000" //unix timestamp in milliseconds<br />
}<br />
</syntaxhighlight><br />
<br />
'''Note:''' The full payload including "devInfo" and "metadata" must be populated in each event request.<br />
<br />
===Example Image Request===<br />
<br />
In order to execute Cloud API calls, image requests will need to be established for each event.<br />
<br />
===Playhead Image Request Example Code===<br />
<br />
<syntaxhighlight lang="javascript"><br />
playheadRequest = CreateObject("roUrlTransfer")<br />
sendUrl = sessionUrl+playheadRequest.Escape(playheadPayload)<br />
playheadRequest.SetUrl(sendUrl)<br />
xport=CreateObject("RoMessagePort")<br />
playheadRequest.setport(xport)<br />
aa3 = {}<br />
aa3["Connection"] = "keep-alive"<br />
aa3["Content-type"] = "image/gif"<br />
playheadRequest.SetHeaders(aa3)<br />
playheadRequest.setRequest("GET")<br />
</syntaxhighlight><br />
<br />
===== Sample Event Lifecycle =====<br />
The sample event lifecycle can be used as a reference for identifying the order for calling events and values to pass.<br />
<br />
<syntaxhighlight lang="javascript"><br />
// Start of Session: session ID created when App is opened<br />
<br />
// Preroll<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
// Content<br />
Content Playhead {"event": "playhead", "position": "0", "type": "content", "utc": "1472760000000"} <br />
<br />
// Midroll<br />
Midroll Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
// Content resumes at 15 minutes<br />
Content Playhead {"event": "playhead", "position": "900", "type": "content", "utc": "1472760000000"} <br />
<br />
// Content completes at 30 minutes<br />
Complete {"event": "complete", "position": "1800", "type": "content", "utc": "1472760000000"} <br />
<br />
// Postroll<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
//End of Session: The delete event should be called when the App is exited. The values for position and type not required to be passed.<br />
Delete { "event": "delete", "position": "", "type": "", "utc": "1472760000000"} <br />
</syntaxhighlight><br />
<br />
<br />
'''Sample Event Lifecycle - Detailed Storyline'''<br />
This detailed event sequence provides additional insight for the correct events to call when handling certain playback scenarios.<br />
<syntaxhighlight lang='javascript'>// SESSION STARTS<br />
// Start of Session: session ID created when App is opened<br />
<br />
// PREROLL<br />
// Preroll Start - Start each Ad with a position of "0", resetting to '0' for each Ad, and Ad break.<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
// Preroll Stop - End each Ad with the final position of the Ad.<br />
Ad Playhead {"event": "playhead", "position": "15", "type": "ad", "utc": "1472761500000"}<br />
<br />
// CONTENT <br />
// Content Start - Start new content streams with a position of "0" incrementing the position every 10 seconds.<br />
Content Playhead {"event": "playhead", "position": "0", "type": "content", "utc": "1472761500000"} <br />
<br />
// Content Stop Before Ad Break - Send a playhead update including the current content positon before an Ad break.<br />
Content Playhead {"event": "playhead", "position": "299", "type": "content", "utc": "1472787400000"}<br />
<br />
// MIDROLL<br />
// Midroll Start - Start each Ad with a position of "0", resetting to '0' for each Ad, and Ad break.<br />
Midroll Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472787500000"} <br />
<br />
// Midroll Stop - End each Ad with the final position of the Ad.<br />
Ad Playhead {"event": "playhead", "position": "60", "type": "ad", "utc": "1472793500000"}<br />
<br />
// CONTENT<br />
// Content resumes at 5 minutes - Send playhead update with the current resumed position, and begin incrimenting the positon every 10 seconds.<br />
Content Playhead {"event": "playhead", "position": "300", "type": "content", "utc": "1472799500000"} <br />
<br />
// Content completes at 10:12 - Make sure to send in the playhead event with the final content position before sending the complete event.<br />
Final Content Playhead {"event": "playhead", "position": "612", "type": "content", "utc": "1472830700000"} <br />
<br />
Complete {"event": "complete", "position": "612", "type": "content", "utc": "1472830800000"} <br />
<br />
// POSTROLL<br />
// Postroll Start - Start each Ad with a position of "0", resetting to '0' for each Ad, and Ad break.<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472830900000"} <br />
<br />
// Postroll Stop - End each Ad with the final position of the Ad.<br />
Ad Playhead {"event": "playhead", "position": "45", "type": "ad", "utc": "1472835300000"}<br />
<br />
// SESSION ENDS<br />
<br />
//End of Session: The delete event should be called when the App is exited. The values for position and type not required to be passed.<br />
Delete { "event": "delete", "position": "", "type": "", "utc": "1472835400000"} </syntaxhighlight><br />
<br />
<br />
=====Handling Playhead=====<br />
Calling <code>"playhead"</code> is critical for accurate duration crediting. You can reference the below guidance to determine the correct playhead position to pass depending on the playback scenario.<br />
<br />
'''Playhead: General'''<br />
* Playhead position must start at 0 for each new asset, and be passed at least every 10 seconds.<br />
* Final postion must be sent at the end of content or an ad<br />
<br />
'''Playhead: Ads'''<br />
* The final position must be sent when switching from content to ad, or ad to content.<br />
* Each ad playhead position should be 0 at ad start.<br />
* For Ad Pods, playhead must be called, and reset to 0 for each individual ad. <br />
* The last content position before an Ad should be sent before switching to Ads.<br />
* When content has resumed following an ad break, the playhead position update must continue where the previous content segment left off.<br />
<br />
'''Playhead: User Actions'''<br />
* Upon user scrubbing, the current position must be sent before a user scrubs, and the new position should be sent where the user lands, and begin sending in the 10 second updates thereafter.<br />
* On pause, send the current position and then discontinue sending playhead event updates.<br />
* If a user exits a stream early, the last current position must be sent in a playhead update to receive accurate duration.<br />
<br />
===== Interruption Scenarios =====<br />
<br />
As part of configuring events, you will need to handle all possible interruption scenarios such as:<br />
<br />
*Wi-Fi OFF / ON<br />
*App going Background / Foreground (Video players only, not for Audio players)<br />
*App Crash or Exit<br />
<br />
When playback is interrupted, the app needs to send delete immediately.<br />
<br />
Once playback resumes, a new session will need to be created with a unique session ID. All of the required metadata and events will need to be sent.<br />
<br />
'''Note:''' The session will automatically timeout after 30 minutes of inactivity.<br />
<br />
=== Example Request ===<br />
<br />
Now that we walked through the Cloud API integration steps, your requests should have the following components: Session ID, App ID, and Payload. You can reference the example below when your reviewing your integration.<br />
<br />
<syntaxhighlight lang="javascript"><br />
// 1. Create Session ID<br />
sessionID = dfc7dc6a-66a7-4705-9fba-adaaf7e3d5e0 // Example sessionID created using a UUID Generator<br />
<br />
// 2. Define URL Structure with App ID and Session ID<br />
sessionURL = https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=<br />
<br />
// 3. Configure Payload<br />
// 3.1 Configure Payload: devInfo <br />
payload = {<br />
"devInfo": {<br />
"encdata": "encdata=v1.key.RokuAES256CB...",<br />
"apn": "AppName",<br />
"apv": "1.0",<br />
"uoo": "false"<br />
},<br />
<br />
// 3.2 Configure Payload: metadata<br />
"metadata": {<br />
"static": {}, // object for measuring static content<br />
"content": { // object for measuring video content<br />
"type": "content", // "content" for video<br />
"assetid": "VIDEO-ID123", // unique ID for video<br />
"isfullepisode": "y", // full episode flag<br />
"program": "Program Name", // program name<br />
"title": "Episode Title S3 - EP1", // episode name<br />
"length": "1800", // content duration in seconds<br />
"segB": "Custom Segment B", // custom segment<br />
"segC": "Custom Segment C", // custom segment<br />
"crossId1": "Standard Episode ID", // episode ID<br />
"crossId2": "Content Originator ID", // content orginator (required for distributors)<br />
"airdate": "20161013 20:00:00", // airdate<br />
"adloadtype": "2", // ad load flag<br />
"hasAds": "1", // content contains ads = 1 / no ads = 0<br />
"progen": "CV" // program genre abbreviation<br />
},<br />
"ad": {<br />
"type": "preroll", // type of ad<br />
"assetid": "AD-ID123" // unique ID for ad<br />
}<br />
},<br />
<br />
// 3.3 Configure Payload: events<br />
"event": "playhead", //event name<br />
"position": "300", // position in seconds<br />
"type": "content", //"content" or "ad"<br />
"utc": "1456448742000" //unix timestamp in milliseconds <br />
}<br />
<br />
// Append payload to URL<br />
sendUrl = sessionUrl.Escape(payload)<br />
</syntaxhighlight><br />
<br />
==== Enable Debug Logging ====<br />
<br />
Now that you have set up the Cloud API requests, you can enable debug logging to validate your integration. Enabling debug logging is required for Nielsen certification.<br />
<br />
==== GET Request ====<br />
<br />
Display GET Request to console using a name to identify each event (e.g. playhead).<br />
<br />
<syntaxhighlight lang="javascript"><br />
print "Playhead Event " + sendUrl<br />
</syntaxhighlight><br />
<br />
==== Payload ====<br />
<br />
Output payload to identify required metadata and events.<br />
<br />
<syntaxhighlight lang="javascript"><br />
print "Event Payload "+ playheadPayload + modSeq%.tostr()<br />
</syntaxhighlight><br />
<br />
==== HTTP Response Code ====<br />
<br />
Confirm request was completed by viewing HTTP response code.<br />
<br />
<syntaxhighlight lang="javascript"><br />
httpCode = GetResponseCode()<br />
print "Response Code " +httpCode.tostr()<br />
</syntaxhighlight><br />
<br />
You can reference the HTTP Response Code table when reviewing your requests:<br />
<br />
{| class="wikitable"<br />
|-<br />
! Status Code !! Status Text !! Description<br />
|-<br />
| <code>200</code> || OK || request received<br />
|-<br />
| <code>403</code> || Forbidden || invalid App ID<br />
|-<br />
| <code>404</code> || Not Found || JSON issue<br />
|}<br />
<br />
== Opt-Out ==<br />
<br />
Your app must provide a means for the user to Opt-Out, or Opt-In to Nielsen Measurement. This can be implemented in two steps:<br />
<br />
==== Step 1: Nielsen Privacy Policy & Roku Channel Disclosure ====<br />
<br />
In your application, you must display the Nielsen privacy policy, which instructs users on how to opt out and opt in to Nielsen measurements. This text is usually displayed in an application's "Settings" or "About" screens.<br />
<br />
<blockquote><br />
'''''ABOUT NIELSEN MEASUREMENT'''''<br />
<br />
Television and the way we watch it have come a long way since Nielsen began measuring TV audiences in 1950. Today, the ability to watch our favorite shows at any time and on multiple devices amplifies the need for exceptionally adept and flexible audience measurement capabilities.<br />
<br />
Consumers are changing with the times, and the same goes for us. As technology continues to evolve and media companies try new ways to attract viewers, understanding what consumers are watching — and what they're watching on — is more important than ever. Today, viewing video is a personal and mobile experience — anytime and anywhere. Our capabilities provide relevant metrics that are necessary to inform successful marketing and programming and drive continued growth. As a global information and measurement leader, we are committed to protecting the privacy and security of the data we collect, process and use. While our digital measurement products are not used to identify you in any way, they help us and our clients measure and analyze how consumers engage with media across online, mobile and emerging technologies, and offer insights into consumer behavior.<br />
<br />
'''''YOUR CHOICES'''''<br />
<br />
Nielsen believes that you should have a choice about whether to contribute to our research and insights. To opt out, or opt into Nielsen measurement please choose the appropriate "Limit Ad Tracking" setting on your device. If you have this app on more than one device, you will need to opt out of this app on each device. To learn more about our digital measurement products and your choices in regard to them, please visit http://priv-policy.imrworldwide.com/priv/browser/us/en/optout.html.<br />
</blockquote><br />
<br />
In addition, on your Roku Channel description, you should add the following disclosure:<br />
<br />
<blockquote><br />
This app features Nielsen's proprietary measurement software which will allow you to contribute to market research, like Nielsen's TV Ratings. Please see http://priv-policy.imrworldwide.com/priv/browser/us/en/optout.html for more information.<br />
</blockquote><br />
<br />
==== Step 2: Use "Limit Ad Tracking" setting to set User Opt Out ====<br />
<br />
Depending on the user's selection for the "Limit Ad Tracking" device setting, you should set the Cloud API <code>"uoo"</code> parameter accordingly. Roku provides an API called [https://sdkdocs.roku.com/display/sdkdoc/ifDeviceInfo#ifDeviceInfo-IsAdIdTrackingDisabled()asBoolean IsAdIdTrackingDisabled()] to check the user's limit ad tracking setting.<br />
<br />
{| class="wikitable"<br />
|-<br />
! uoo Key !! Description !! Values<br />
|-<br />
| uoo || Device is Opted-In to Nielsen Measurement || <code>"false"</code><br />
|-<br />
| uoo || Device is Opted-Out of Nielsen Measurement || <code>"true"</code><br />
|}<br />
<br />
The <code>"uoo"</code> parameter is located in the <code>"devInfo"</code> JSON object, which will be sent in every Cloud API event (playhead, complete, & delete).<br />
<br />
===== devInfo Opt-In JSON Payload Example =====<br />
<br />
<syntaxhighlight lang="javascript"><br />
"devInfo": {<br />
"apn": "Roku Sample App",<br />
"apv": "1",<br />
"encdata": "encdata=v1.key.RokuAES256CB...",<br />
"uoo": "false"<br />
},<br />
</syntaxhighlight><br />
<br />
===== devInfo Opt-Out JSON Payload Example =====<br />
<br />
<syntaxhighlight lang="javascript"><br />
"devInfo": {<br />
"apn": "Roku Sample App",<br />
"apv": "1",<br />
"encdata": "", //Encdata must be blank when a user elects to Opt-Out.<br />
"uoo": "true"<br />
},<br />
</syntaxhighlight><br />
<br />
Your application should check the Limit Ad Tracking setting on each app start, and/or periodically, to ensure the user's ad tracking selection is reflected in the Nielsen opt-out setting in your app.<br />
<br />
*In addition, you will need to ensure that the encdata field is populated with a blank value if a user has elected to Opt-Out.<br />
<br />
== Testing ==<br />
Before providing an app build to Nielsen for testing, it is important to run validation checks once you have enabled debug logging.<br />
<br />
=== Payload Validation ===<br />
<br />
Ensure that all of the required payload data is populating while testing several videos. The following areas are critical to measurement:<br />
*devInfo<br />
*Asset metadata for both content, and ads<br />
*Events<br />
*Opt-Out status<br />
<br />
=== Player Events ===<br />
Review event calls:<br />
<br />
==== playhead ====<br />
*Playhead position updates every 10 seconds starting at position '0' for each new asset.<br />
*Final playhead position is sent on content, or ad before switching between assets.<br />
*Content metadata remains constant throughout an episode, or clip play.<br />
*Ad metadata is populated appropriately for each individual ad.<br />
*Playhead position update resumes for content after an ad break, and resets to 0 for each individual ad.<br />
*For scrubbing, last current position should be sent while scrubbing occurs, and the new position should also be sent where the user scrubs to.<br />
*Exiting a stream early should execute the last current position in a playhead update to receive accurate duration.<br />
*Upon pause, the current position should be sent, and playhead updates should stop incrementing until resume play occurs.<br />
<br />
==== complete ====<br />
*Check that the complete event executes upon content complete after the final playhead update is sent.<br />
*Do not execute the complete event for ads<br />
<br />
==== delete ====<br />
*Check to see that the delete event occurs upon app exit<br />
<br />
==== GET Request Format ====<br />
*Ensure that the event payloads are formatted in JSON<br />
*Check to see that each of the Cloud API GET requests are properly encoded.<br />
<br />
==== HTTP Response ====<br />
*Make sure that each of the Cloud API Get requests are received by the Nielsen Cloud API properly through use of the HTTP Response Code outputs enabled in console.<br />
<br />
==== Opt-Out ====<br />
*Test the "uoo" key gets populated accurately for both Opt-In and Opt-Out selections based on the device's "Limit Ad Tracking" setting.<br />
*Test that the encdata field is populated with a blank value if a user has elected to Opt-Out. For Example: "encdata": "",<br />
<br />
== Go Live ==<br />
After your integration has been certified, you will need to: Change Endpoint and Disable Logging.<br />
<br />
'''Change Endpoint:''' You will need to update to the production endpoint:<br />
<br />
*Testing: <code>https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/</code><br />
*Production: <code>https://cloudapi.imrworldwide.com/nmapi/v2/</code><br />
<br />
Your production URL structure should now be:<br />
<code>https://cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=[payload]</code><br />
<br />
'''Disable Logging:''' You can now disable debug logging.</div>AlexGutierrezhttps://engineeringportal.nielsen.com//w/index.php?title=DCR_Video_%26_Static_Mobile_Cloud_API&diff=3625DCR Video & Static Mobile Cloud API2019-05-03T16:43:12Z<p>AlexGutierrez: Changes to event types descriptions per Engineering feedback</p>
<hr />
<div>{{Breadcrumb|}} {{Breadcrumb|Digital}} {{Breadcrumb|DCR & DTVR}} {{CurrentBreadcrumb}}<br />
[[Category:Digital]]<br />
<br />
This guide shows you how to integrate the Nielsen Cloud API to enable Digital Content Ratings (DCR) measurement on your mobile apps.<br />
*For Roku Apps, please see [[DCR Video & Static Roku Cloud API]]<br />
*For OTT Devices, please see [[DCR Video & Static Cloud API]]<br />
<br />
==Prerequisites==<br />
To get started, you will need a Nielsen App ID. The App ID is a unique ID assigned to your app. This will be provided to you upon starting the integration.<br />
<br />
<syntaxhighlight lang="javascript">XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX</syntaxhighlight><br />
<br />
==Integration==<br />
We will cover the steps for constructing the Cloud API Calls.<br />
<br />
===Request Overview===<br />
<br />
====URL Structure====<br />
<br />
The Cloud API Calls are HTTP GET Requests with the URL structure:<br />
<br />
<syntaxhighlight lang="javascript">[endpoint]/[appid]/[sessionID]/a?b=[payload]</syntaxhighlight><br />
<br />
The URL includes the following components:<br />
<br />
*<code>[endpoint]</code>: location of data collection environment<br />
*<code>[appid]</code>: provided App ID<br />
*<code>[sessionID]</code>: unique value for each user session<br />
*<code>[payload]</code>: metadata and events<br />
<br />
====Endpoint====<br />
<br />
There are endpoints for testing and production:<br />
<br />
*Testing: <code>https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/</code><br />
*Production: <code>https://cloudapi.imrworldwide.com/nmapi/v2/</code><br />
<br />
During testing, all calls should be pointed to the testing endpoint. We will review the update to the production endpoint during the Go Live section of this guide.<br />
<br />
====URL Example====<br />
As you move through the integration steps, you can reference the below URL structure with the expanded payload:<br />
<br />
<syntaxhighlight lang="javascript"><br />
https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=<br />
{<br />
"devInfo": [deviceInfo],<br />
"metadata": {<br />
"static": [static_metadata],<br />
"content": [content_metadata],<br />
"ad": [ad metadata]<br />
},<br />
"event": [event],<br />
"position": [playhead_position],<br />
"type": [asset type],<br />
"utc": [UTC]<br />
}<br />
</syntaxhighlight><br />
<br />
===Create Session ID===<br />
A unique Session ID must be created upon app launch and provided in the URL. This will allow measurement to occur for the entire duration that a user is within the app.<br />
<br />
A Session ID needs to be completely unique so it is recommended to use a version 4 UUID or another method of your choosing to guarantee there are no repeats.<br />
<br />
Upon exiting the app, the session will need to be terminated using the delete event. Sessions will automatically expire after 30 minutes of cloud inactivity.<br />
<br />
===Define URL Structure===<br />
Define the URL structure using your provided <code>[appid]</code> and a unique <code>[sessionID]</code>.<br />
<br />
<syntaxhighlight lang="javascript">https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=[payload]</syntaxhighlight><br />
<br />
===Configure Payload===<br />
<br />
All Cloud API requests must contain the following payload data:<br />
<br />
*''devInfo'': device and app info<br />
*''metadata'': asset metadata<br />
*''event metadata'': type of event<br />
<br />
The payload can be passed through key-values using the Nielsen reserved keys. The specific keys and descriptions are highlighted in the tables included in this section.<br />
<br />
'''Payload Example'''<br />
<br />
The example below should be referenced when following the steps for configuring the request payload.<br />
<br />
<syntaxhighlight lang="javascript"><br />
// 3.1 Configure Payload: devInfo <br />
payload = {<br />
"devInfo": {<br />
"devId": "AD-ID", <br />
"apn": "AppName",<br />
"apv": "1.0",<br />
"uoo": "false"<br />
},<br />
<br />
// 3.2 Configure Payload: metadata<br />
"metadata": {<br />
"static": {}, // object for measuring static content<br />
"content": { // object for measuring video content<br />
"type": "content", // "content" for video<br />
"assetid": "VIDEO-ID123", // unique ID for video<br />
"isfullepisode": "y", // full episode flag<br />
"program": "Program Name", // program name<br />
"title": "Episode Title S3 - EP1", // episode name<br />
"length": "1800", // content duration in seconds<br />
"segB": "Custom Segment B", // custom segment<br />
"segC": "Custom Segment C", // custom segment<br />
"crossId1": "Standard Episode ID", // episode ID<br />
"crossId2": "Content Originator ID", // content orginator (required for distributors)<br />
"airdate": "20161013 20:00:00", // airdate<br />
"adloadtype": "2" //ad load flag<br />
"hasAds": "1", // content contains ads = 1 / no ads = 0<br />
"progen": "CV" // program genre abbreviation<br />
},<br />
"ad": {<br />
"type": "preroll", // type of ad<br />
"assetid": "AD-ID123" // unique ID for ad<br />
}<br />
},<br />
<br />
// 3.3 Configure Payload: events<br />
"event": "playhead", //event name<br />
"position": "300", // position in seconds<br />
"type": "content", //"content" or "ad"<br />
"utc": "1456448742000" //unix timestamp in milliseconds <br />
}<br />
</syntaxhighlight><br />
<br />
=====Configure Payload: devInfo=====<br />
An object <code>"devInfo"</code> will need to be created to capture App and Device information.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| devId || unique ID to identify user (e.g. Advertising ID, Roku Device ID) || custom || Yes<br />
|-<br />
| apn || app name || custom || Yes<br />
|-<br />
| apv || app build version || custom || Yes<br />
|-<br />
| uoo || device opt-out status || <code>"true"</code> or <code>"false"</code> || Yes<br />
|-<br />
|}<br />
<br />
On mobile devices you will need to pass the IDFA/Ad ID as part of the devId parameter. Please see the section below regarding Opt Out and the IDFA/Ad ID.<br />
<br />
===== Accessing the IDFA on iOS =====<br />
<br />
Accessing the ID For Advertisers (IDFA) uses OS level API calls. In order to do this, you must first import the following header file:<br />
<syntaxhighlight lang="objective-c">#import <AdSupport/ASIdentifierManager.h></syntaxhighlight><br />
<br />
Then to receive the IDFA as a NSString, use similar code to the following:<br />
<syntaxhighlight lang="objective-c">NSString *idfaString = [[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString];</syntaxhighlight><br />
You can read more about ASIdentifierManager on Apple's [https://developer.apple.com/reference/adsupport/asidentifiermanager| developer website].<br />
<br />
===== Accessing the Ad ID on Android =====<br />
<br />
Accessing the Google Ad ID uses OS level API calls. In order to do this, you must first import Google Play Services. Then to receive the Ad ID, use the AdvertisingIdClient class as such:<br />
<syntaxhighlight lang="java">public static AdvertisingIdClient.Info getAdvertisingIdInfo (Context context)</syntaxhighlight><br />
<br />
You can read more about the AdvertisingIdClient class on Google's [https://developers.google.com/android/reference/com/google/android/gms/ads/identifier/AdvertisingIdClient| developer website].<br />
<br />
'''Example devInfo Object'''<br />
<syntaxhighlight lang="javascript"><br />
// create devInfo object<br />
"devInfo": {<br />
"devId": "AD-ID",<br />
"apn": "AppName",<br />
"apv": "1.0",<br />
"uoo": "false"<br />
},<br />
</syntaxhighlight><br />
<br />
==== 3.2 Configure Payload: metadata ====<br />
Asset metadata can be passed through <code>"metadata"</code>. There are two asset types: <code>"content"</code> for video and <code>"ad"</code> for ads. The metadata received for each asset is used for classification and reporting.<br />
<br />
You will need to set up <code>"metadata"</code> objects for <code>"content"</code> and <code>"ad"</code> with the required Nielsen keys as shown in the sample code below.<br />
<br />
===== Content Metadata =====<br />
Content metadata should remain constant throughout the entirety of an episode/clip including when ads play.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| type || type of asset || <code>"content"</code> || Yes<br />
|-<br />
| assetid || unique ID assigned to asset || custom || Yes<br />
|-<br />
| program ||name of program (25 character limit) || custom || Yes<br />
|-<br />
| title ||name of program (25 character limit) || custom || Yes<br />
|-<br />
| length || length of content in seconds || <code>seconds</code> (0 for live stream) || Yes<br />
|-<br />
| segB || custom segment B || custom || <br />
|-<br />
| segC || custom segment C || custom || <br />
|-<br />
| airdate || the airdate in the linear TV || YYYYMMDD HH24:MI:SS || Yes<br />
|-<br />
| isfullepisode || full episode flag || <code>"y"</code>- full episode, <code>"n"</code>- non full episode || Yes<br />
|-<br />
| crossId1 || standard episode ID || custom || Yes<br />
|-<br />
| crossId2 || content originator (only required for distributors) || Nielsen ||<br />
|-<br />
| adloadtype || type of ad load:<br />
<code>"1"</code> Linear – matches TV ad load<br />
<br />
<code>"2"</code> Dynamic – Dynamic Ad Insertion (DAI)<br />
|| <code>"2"</code> - DCR measures content with dynamic ads || Yes<br />
|-<br />
| hasAds || ads indicator<br />
<code>"1"</code>: ads included<br />
<br />
<code>"0"</code>: ads not included<br />
|| <code>"1"</code> or <code>"0"</code> || Yes<br />
|-<br />
| progen || program genre abbreviation - see [[DCR OTT Genre List]] for accepted values || <code>"CV"</code> for Comedy Variety || Yes<br />
|}<br />
<br />
<br />
'''Example Content Object'''<br />
<syntaxhighlight lang='json'>// create content object<br />
"content": {<br />
"type": "content",<br />
"assetid": "VIDEO-ID123",<br />
"isfullepisode": "y",<br />
"program": "Program Name",<br />
"title": "Episode Title S3 - EP1",<br />
"length": "1800",<br />
"segB": "Custom Segment B",<br />
"segC": "Custom Segment C",<br />
"crossId1": "Standard Episode ID",<br />
"crossId2": "Content Originator ID",<br />
"airdate": "20161013 20:00:00",<br />
"adloadtype": "2",<br />
"hasAds": "1", <br />
"progen": "CV"<br />
}</syntaxhighlight><br />
<br />
===== Ad Metadata =====<br />
The ad metadata should be passed for each individual ad.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| type || type of ad || <code>"preroll"</code>, <code>"midroll"</code>, or <code>"postroll"</code> || Yes<br />
|-<br />
| assetid || unique ID assigned to ad || custom || Yes<br />
|}<br />
<br />
===== Example Ad Object =====<br />
<syntaxhighlight lang="javascript"><br />
// create ad object<br />
"ad": {<br />
"type": "preroll",<br />
"assetid": "AD-ID123"<br />
}<br />
</syntaxhighlight><br />
<br />
=== Configure Payload: Events ===<br />
<br />
The last part of the payload is for enabling events so content is measured correctly when viewed. The events and required parameters are included below.<br />
<br />
==== Event Types ====<br />
<br />
The available events are:<br />
{| class="wikitable"<br />
|-<br />
! Event !! Description<br />
|-<br />
| <code>"playhead"</code> || Playhead position in seconds, must be passed as a whole number every 10 seconds. The final playhead position should be sent before an asset has changed to properly capture full duration. When content is paused, stop passing playhead position until content is resumed. On playhead scrubbing, send current playhead position, followed by the playhead position the user scrubs to. For Live streams, you may use Unix Time (in seconds) as the playhead position. Note that ad playheads must also use Unix Time if Unix Time is used for content playheads.<br />
|-<br />
| <code>"complete"</code> || The complete event must be sent when the content has completed full playback. Before calling the complete event, a final playhead update with the final position is required to be sent to receive full duration credit. For Live streams, a complete event must be sent at program boundaries.<br />
|-<br />
| <code>"delete"</code> || The delete event is optional and can be sent when the viewing session is terminated (typically on App close). A new session ID must be generated after sending a delete event. Delete should not be sent on app interruptions or foreground/background events. All creditable duration will be summarized for all asset types when delete occurs (content and ads).<br />
|}<br />
<br />
===== Event Parameters =====<br />
<br />
The following parameters need to be passed when calling events:<br />
<br />
{| class="wikitable"<br />
|-<br />
! Parameter !! Description !! Value !! Required<br />
|-<br />
| <code>"event"</code> || event type || <code>"playhead"</code>, <code>"complete"</code>, or <code>"delete"</code> || Yes<br />
|-<br />
| <code>"position"</code> || creditable position || playhead position in seconds or Unix timestamp (seconds since Jan-1-1970 UTC) for livestream || Yes<br />
|-<br />
| <code>"type"</code> || asset type || <code>"content"</code>, <code>"ad"</code> || Yes<br />
|-<br />
| <code>"utc"</code> || Unix timestamp in milliseconds. Must be passed every 10 seconds. || <code>"1472760000000"</code> || Yes<br />
|}<br />
<br />
===== Example Event =====<br />
You can call events by passing values in the required parameters:<br />
<br />
<syntaxhighlight lang="javascript"><br />
"devInfo": [deviceInfo],<br />
"metadata": {<br />
"static": [static metadata],<br />
"content": [content metadata],<br />
"ad": [ad metadata]<br />
},<br />
// Event Parameters<br />
"event": [event], // event name<br />
"position": [playheadPosition], //position in seconds<br />
"type": [asset type], // values are "content" or "ad"<br />
"utc": "1472760000000" //unix timestamp in milliseconds<br />
}<br />
</syntaxhighlight><br />
<br />
'''Note:''' The full payload including "devInfo" and "metadata" must be populated in each event request.<br />
<br />
===== Sample Event Lifecycle =====<br />
The sample event lifecycle can be used as a reference for identifying the order for calling events and values to pass.<br />
<br />
<syntaxhighlight lang="javascript"><br />
// Start of Session: session ID created when App is opened<br />
<br />
// Preroll<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
// Content<br />
Content Playhead {"event": "playhead", "position": "0", "type": "content", "utc": "1472760000000"} <br />
<br />
// Midroll<br />
Midroll Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
// Content resumes at 15 minutes<br />
Content Playhead {"event": "playhead", "position": "900", "type": "content", "utc": "1472760000000"} <br />
<br />
// Content completes at 30 minutes<br />
Complete {"event": "complete", "position": "1800", "type": "content", "utc": "1472760000000"} <br />
<br />
// Postroll<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
//End of Session: The delete event should be called when the App is exited. The values for position and type not required to be passed.<br />
Delete { "event": "delete", "position": "", "type": "", "utc": "1472760000000"} <br />
</syntaxhighlight><br />
<br />
<br />
'''Sample Event Lifecycle - Detailed Storyline'''<br />
This detailed event sequence provides additional insight for the correct events to call when handling certain playback scenarios.<br />
<syntaxhighlight lang='javascript'>// SESSION STARTS<br />
// Start of Session: session ID created when App is opened<br />
<br />
// PREROLL<br />
// Preroll Start - Start each Ad with a position of "0", resetting to '0' for each Ad, and Ad break.<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
// Preroll Stop - End each Ad with the final position of the Ad.<br />
Ad Playhead {"event": "playhead", "position": "15", "type": "ad", "utc": "1472761500000"}<br />
<br />
// CONTENT <br />
// Content Start - Start new content streams with a position of "0" incrementing the position every 10 seconds.<br />
Content Playhead {"event": "playhead", "position": "0", "type": "content", "utc": "1472761500000"} <br />
<br />
// Content Stop Before Ad Break - Send a playhead update including the current content positon before an Ad break.<br />
Content Playhead {"event": "playhead", "position": "299", "type": "content", "utc": "1472787400000"}<br />
<br />
// MIDROLL<br />
// Midroll Start - Start each Ad with a position of "0", resetting to '0' for each Ad, and Ad break.<br />
Midroll Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472787500000"} <br />
<br />
// Midroll Stop - End each Ad with the final position of the Ad.<br />
Ad Playhead {"event": "playhead", "position": "60", "type": "ad", "utc": "1472793500000"}<br />
<br />
// CONTENT<br />
// Content resumes at 5 minutes - Send playhead update with the current resumed position, and begin incrimenting the positon every 10 seconds.<br />
Content Playhead {"event": "playhead", "position": "300", "type": "content", "utc": "1472799500000"} <br />
<br />
// Content completes at 10:12 - Make sure to send in the playhead event with the final content position before sending the complete event.<br />
Final Content Playhead {"event": "playhead", "position": "612", "type": "content", "utc": "1472830700000"} <br />
<br />
Complete {"event": "complete", "position": "612", "type": "content", "utc": "1472830800000"} <br />
<br />
// POSTROLL<br />
// Postroll Start - Start each Ad with a position of "0", resetting to '0' for each Ad, and Ad break.<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472830900000"} <br />
<br />
// Postroll Stop - End each Ad with the final position of the Ad.<br />
Ad Playhead {"event": "playhead", "position": "45", "type": "ad", "utc": "1472835300000"}<br />
<br />
// SESSION ENDS<br />
<br />
//End of Session: The delete event should be called when the App is exited. The values for position and type not required to be passed.<br />
Delete { "event": "delete", "position": "", "type": "", "utc": "1472835400000"} </syntaxhighlight><br />
<br />
<br />
=====Handling Playhead=====<br />
Calling <code>"playhead"</code> is critical for accurate duration crediting. You can reference the below guidance to determine the correct playhead position to pass depending on the playback scenario.<br />
<br />
'''Playhead: General'''<br />
* Playhead position must start at 0 for each new asset, and be passed at least every 10 seconds.<br />
* Final postion must be sent at the end of content or an ad<br />
<br />
'''Playhead: Ads'''<br />
* The final position must be sent when switching from content to ad, or ad to content.<br />
* Each ad playhead position should be 0 at ad start.<br />
* For Ad Pods, playhead must be called, and reset to 0 for each individual ad. <br />
* The last content position before an Ad should be sent before switching to Ads.<br />
* When content has resumed following an ad break, the playhead position update must continue where the previous content segment left off.<br />
<br />
'''Playhead: User Actions'''<br />
* Upon user scrubbing, the current position must be sent before a user scrubs, and the new position should be sent where the user lands, and begin sending in the 10 second updates thereafter.<br />
* On pause, send the current position and then discontinue sending playhead event updates.<br />
* If a user exits a stream early, the last current position must be sent in a playhead update to receive accurate duration.<br />
<br />
===== Interruption Scenarios =====<br />
<br />
As part of configuring events, you will need to handle all possible interruption scenarios such as:<br />
<br />
*Pause / Play<br />
*Network Loss (Wi-Fi / Airplane Mode / Cellular)<br />
*Wi-Fi OFF / ON<br />
*Call Interrupt (SIM or Third party Skype / Hangout call)<br />
*Alarm Interrupt<br />
*Content Buffering<br />
*Device Lock / Unlock (Video players only, not for Audio players)<br />
*App going Background / Foreground (Video players only, not for Audio players)<br />
*App Crash or Exit<br />
*Channel / Station Change Scenario<br />
*Unplugging of headphone<br />
<br />
When playback is temporarily interrupted (e.g. pause, content buffering), the app needs to send the last known playhead position.<br />
<br />
*If an app is sent to background for more than 5 minutes, please create a new session ID. Otherwise, use the same session ID as before.<br />
*If loss of Internet occurs, please queue the API calls that would have been made. Once Internet connectivity is regained, please spool off the API calls in order of first generated (Note: if doing so, please use the UTC time in milliseconds)<br />
<br />
When playback is permanently interrupted, the app needs to send delete immediately.<br />
*If an app crashes, please create a new session ID. No delete call will be necessary as the previous session will timeout.<br />
<br />
Once playback resumes after delete occurs, a new session will need to be created with a unique session ID. All of the required metadata and events will need to be sent.<br />
<br />
'''Note:''' The session will automatically timeout after 30 minutes of inactivity.<br />
<br />
=== Example Request ===<br />
<br />
Now that we walked through the Cloud API integration steps, your requests should have the following components: Session ID, App ID, and Payload. You can reference the example below when your reviewing your integration.<br />
<br />
<syntaxhighlight lang="javascript"><br />
// 1. Create Session ID<br />
sessionID = dfc7dc6a-66a7-4705-9fba-adaaf7e3d5e0 // Example sessionID created using a UUID Generator<br />
<br />
// 2. Define URL Structure with App ID and Session ID<br />
sessionURL = https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=<br />
<br />
// 3. Configure Payload<br />
// 3.1 Configure Payload: devInfo <br />
payload = {<br />
"devInfo": {<br />
"devId": "AD-ID", <br />
"apn": "AppName",<br />
"apv": "1.0",<br />
"uoo": "false"<br />
},<br />
<br />
// 3.2 Configure Payload: metadata<br />
"metadata": {<br />
"static": {}, // object for measuring static content<br />
"content": { // object for measuring video content<br />
"type": "content", // "content" for video<br />
"assetid": "VIDEO-ID123", // unique ID for video<br />
"isfullepisode": "y", // full episode flag<br />
"program": "Program Name", // program name<br />
"title": "Episode Title S3 - EP1", // episode name<br />
"length": "1800", // content duration in seconds<br />
"segB": "Custom Segment B", // custom segment<br />
"segC": "Custom Segment C", // custom segment<br />
"crossId1": "Standard Episode ID", // episode ID<br />
"crossId2": "Content Originator ID", // content orginator (required for distributors)<br />
"airdate": "20161013 20:00:00", // airdate<br />
"adloadtype": "2", // ad load flag<br />
"hasAds": "1", // content contains ads = 1 / no ads = 0<br />
"progen": "CV" // program genre abbreviation<br />
},<br />
"ad": {<br />
"type": "preroll", // type of ad<br />
"assetid": "AD-ID123" // unique ID for ad<br />
}<br />
},<br />
<br />
// 3.3 Configure Payload: events<br />
"event": "playhead", //event name<br />
"position": "300", // position in seconds<br />
"type": "content", //"content" or "ad"<br />
"utc": "1456448742000" //unix timestamp in milliseconds <br />
}<br />
<br />
// Append payload to URL<br />
var image = new Image()<br />
image.onerror = function() {<br />
// wait and send again<br />
}<br />
(new Image).src = sessionURL+encodeURI(JSON.stringify(payload));<br />
</syntaxhighlight><br />
<br />
==== Enable Debug Logging ====<br />
<br />
Now that you have set up the Cloud API requests, you can enable debug logging to validate your integration. Enabling debug logging is required for Nielsen certification.<br />
<br />
==== GET Request ====<br />
<br />
Display GET Request to console using a name to identify each event (e.g. playhead).<br />
<br />
<syntaxhighlight lang="javascript"><br />
console.log("Event", image); <br />
</syntaxhighlight><br />
<br />
==== Payload ====<br />
<br />
Output payload to identify required metadata and events.<br />
<br />
<syntaxhighlight lang="javascript"><br />
console.log("Event Payload", payload); <br />
</syntaxhighlight><br />
<br />
==== HTTP Response Code ====<br />
<br />
Confirm request was completed by viewing HTTP response code.<br />
<br />
<syntaxhighlight lang="javascript"><br />
code = msg.GetResponseCode();<br />
console.log("Response Code", code); <br />
</syntaxhighlight><br />
<br />
You can reference the HTTP Response Code table when reviewing your requests:<br />
<br />
{| class="wikitable"<br />
|-<br />
! Status Code !! Status Text !! Description<br />
|-<br />
| <code>200</code> || OK || request received<br />
|-<br />
| <code>403</code> || Forbidden || invalid App ID<br />
|-<br />
| <code>404</code> || Not Found || JSON issue<br />
|}<br />
<br />
== Opt-Out ==<br />
Your app must provide a means for the user to Opt-Out, or Opt-In to Nielsen Measurement. The Opt-Out requirement can be fulfilled by creating an Opt-Out/Opt-In button, toggle switch, or slider within the app "Settings", or "About" section to allow the user selection. If you have the ability to render text within the Opt-Out screen, it is recommended to add the Privacy Information included below.<br />
<br />
[[File:Nielsen Opt-Out.png|link=]]<br />
<br />
You will need to store the User Opt-Out (uoo) status, so that it can be retrieved and populated in the <code>"devInfo"</code> metadata which will be sent in every Cloud API event (playhead, complete, & delete). <br />
<br />
*In addition, you will need to ensure that the devId field is a blank value if a user has elected to Opt-Out<br />
<br />
*If the device offers "Limit Ad Tracking" or "Opt-Out" device settings, you must set uoo=true, and also ensure that the devId is set to a blank value if the user elects to enable this setting.<br />
<br />
{| class="wikitable"<br />
|-<br />
! uoo Key !! Description !! Values<br />
|-<br />
| uoo || Device is Opted-In to Nielsen Measurement (Recommended Default Setting) || <code>"false"</code><br />
|-<br />
| uoo || Device is Opted-Out of Nielsen Measurement || <code>"true"</code><br />
|}<br />
<br />
===== devInfo Opt-In JSON Payload Example =====<br />
<br />
<syntaxhighlight lang="javascript"><br />
"devInfo": {<br />
"apn": "Roku Sample App",<br />
"apv": "1",<br />
"devId": "7be25cf9-8c40-5cc2-871e-19bf41940288",<br />
"uoo": "false"<br />
},<br />
</syntaxhighlight><br />
<br />
===== devInfo Opt-Out JSON Payload Example =====<br />
<br />
<syntaxhighlight lang="javascript"><br />
"devInfo": {<br />
"apn": "Roku Sample App",<br />
"apv": "1",<br />
"devId": "", //devId must be blank when a user elects to Opt-Out.<br />
"uoo": "true"<br />
},<br />
</syntaxhighlight><br />
<br />
===== Privacy Information Template To Include In Opt-Out Screen =====<br />
<br />
<blockquote><br />
'''''ABOUT NIELSEN MEASUREMENT'''''<br />
<br />
Television and the way we watch it have come a long way since Nielsen began measuring TV audiences in 1950. Today, the ability to watch our favorite shows at any time and on multiple devices amplifies the need for exceptionally adept and flexible audience measurement capabilities.<br />
<br />
Consumers are changing with the times, and the same goes for us. As technology continues to evolve and media companies try new ways to attract viewers, understanding what consumers are watching — and what they're watching on — is more important than ever. Today, viewing video is a personal and mobile experience — anytime and anywhere. Our capabilities provide relevant metrics that are necessary to inform successful marketing and programming and drive continued growth. As a global information and measurement leader, we are committed to protecting the privacy and security of the data we collect, process and use. While our digital measurement products are not used to identify you in any way, they help us and our clients measure and analyze how consumers engage with media across online, mobile and emerging technologies, and offer insights into consumer behavior.<br />
<br />
'''''YOUR CHOICES'''''<br />
<br />
Nielsen believes that you should have a choice about whether to contribute to our research and insights. To opt out, or opt into Nielsen measurement please make the appropriate selection within the app. If you have this app on more than one device, you will need to opt out of this app on each device. To learn more about our digital measurement products and your choices in regard to them, please visit http://www.nielsen.com/digitalprivacy.<br />
<br />
'''''Disclosure Template'''''<br />
<br />
Additionally, you must update the App description information from the App Distribution Store with the Nielsen Measurement disclosure below:<br />
<br />
This app features Nielsen's proprietary measurement software which will allow you to contribute to market research, like Nielsen's TV Ratings. Please see http://www.nielsen.com/digitalprivacy for more information.<br />
</blockquote><br />
<br />
== Testing ==<br />
Before providing an app build to Nielsen for testing, it is important to run validation checks once you have enabled debug logging.<br />
<br />
=== Payload Validation ===<br />
<br />
Ensure that all of the required payload data is populating while testing several videos. The following areas are critical to measurement:<br />
*devInfo<br />
*Asset metadata for both content, and ads<br />
*Events<br />
*Opt-Out status<br />
<br />
=== Player Events ===<br />
Review event calls:<br />
<br />
==== playhead ====<br />
*Playhead position updates every 10 seconds starting at position '0' for each new asset.<br />
*Final playhead position is sent on content, or ad before switching between assets.<br />
*Content metadata remains constant throughout an episode, or clip play.<br />
*Ad metadata is populated appropriately for each individual ad.<br />
*Playhead position update resumes for content after an ad break, and resets to 0 for each individual ad.<br />
*For scrubbing, last current position should be sent while scrubbing occurs, and the new position should also be sent where the user scrubs to.<br />
*Exiting a stream early should execute the last current position in a playhead update to receive accurate duration.<br />
*Upon pause, the current position should be sent, and playhead updates should stop incrementing until resume play occurs.<br />
<br />
==== complete ====<br />
*Check that the complete event executes upon content complete after the final playhead update is sent<br />
*Do not execute the complete event for ads<br />
<br />
==== delete ====<br />
*Check to see that the delete event occurs upon app exit<br />
<br />
==== GET Request Format ====<br />
*Ensure that the event payloads are formatted in JSON<br />
*Check to see that each of the Cloud API GET requests are properly encoded<br />
<br />
==== HTTP Response ====<br />
*Make sure that each of the Cloud API Get requests are received by the Nielsen Cloud API properly through use of the HTTP Response Code outputs enabled in console.<br />
<br />
==== Opt-Out ====<br />
*Test the "uoo" key gets populated accurately for both Opt-In and Opt-Out selections by validating the Cloud API events called after the user Opt-Out/Opt-In selection.<br />
*Test that the devId field is populated with a blank value if a user has elected to Opt-Out. For example: "devId": "",<br />
*If the device supports "Limit Ad Tracking" or has device "Opt-Out" settings, test that uoo=true, and that devId is set to a blank value if enabled in the device settings.<br />
<br />
== Go Live ==<br />
After your integration has been certified, you will need to: Change Endpoint and Disable Logging.<br />
<br />
'''Change Endpoint:''' You will need to update to the production endpoint:<br />
<br />
*Testing: <code>https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/</code><br />
*Production: <code>https://cloudapi.imrworldwide.com/nmapi/v2/</code><br />
<br />
Your production URL structure should now be:<br />
<code>https://cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=[payload]</code><br />
<br />
'''Disable Logging:''' You can now disable debug logging</div>AlexGutierrezhttps://engineeringportal.nielsen.com//w/index.php?title=DCR_Video_%26_Static_Cloud_API&diff=3624DCR Video & Static Cloud API2019-05-03T16:43:06Z<p>AlexGutierrez: Changes to event types descriptions per Engineering feedback</p>
<hr />
<div>{{Breadcrumb|}} {{Breadcrumb|Digital}} {{Breadcrumb|DCR & DTVR}} {{CurrentBreadcrumb}}<br />
[[Category:Digital]]<br />
<br />
This guide shows you how to integrate the Nielsen Cloud API to enable Digital Content Ratings (DCR) measurement on your over-the-top (OTT) Apps.<br />
*For Roku Apps, please see [[DCR Video & Static Roku Cloud API]]<br />
*For Mobile Apps, please see [[DCR Video & Static Mobile Cloud API]]<br />
<br />
==Prerequisites==<br />
To get started, you will need a Nielsen App ID. The App ID is a unique ID assigned to your app. This will be provided to you upon starting the integration.<br />
<br />
<syntaxhighlight lang="javascript">XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX</syntaxhighlight><br />
<br />
==Integration==<br />
We will cover the steps for constructing the Cloud API Calls.<br />
<br />
===Request Overview===<br />
<br />
====URL Structure====<br />
<br />
The Cloud API Calls are HTTP GET Requests with the URL structure:<br />
<br />
<syntaxhighlight lang="javascript">[endpoint]/[appid]/[sessionID]/a?b=[payload]</syntaxhighlight><br />
<br />
The URL includes the following components:<br />
<br />
*<code>[endpoint]</code>: location of data collection environment<br />
*<code>[appid]</code>: provided App ID<br />
*<code>[sessionID]</code>: unique value for each user session<br />
*<code>[payload]</code>: metadata and events<br />
<br />
====Endpoint====<br />
<br />
There are endpoints for testing and production:<br />
<br />
*Testing: <code>https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/</code><br />
*Production: <code>https://cloudapi.imrworldwide.com/nmapi/v2/</code><br />
<br />
During testing, all calls should be pointed to the testing endpoint. We will review the update to the production endpoint during the Go Live section of this guide.<br />
<br />
====URL Example====<br />
As you move through the integration steps, you can reference the below URL structure with the expanded payload:<br />
<br />
<syntaxhighlight lang="javascript"><br />
https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=<br />
{<br />
"devInfo": [deviceInfo],<br />
"metadata": {<br />
"static": [static_metadata],<br />
"content": [content_metadata],<br />
"ad": [ad metadata]<br />
},<br />
"event": [event],<br />
"position": [playhead_position],<br />
"type": [asset type],<br />
"utc": [Unix time in ms]<br />
}<br />
</syntaxhighlight><br />
<br />
===Create Session ID===<br />
A unique Session ID must be created upon app launch and provided in the URL. This will allow measurement to occur for the entire duration that a user is within the app.<br />
<br />
A Session ID needs to be completely unique so it is recommended to use a version 4 UUID or another method of your choosing to guarantee there are no repeats.<br />
<br />
Upon exiting the app, the session will need to be terminated using the delete event. Sessions will automatically expire after 30 minutes of cloud inactivity.<br />
<br />
===Define URL Structure===<br />
Define the URL structure using your provided <code>[appid]</code> and a unique <code>[sessionID]</code>.<br />
<br />
<syntaxhighlight lang="javascript">https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=[payload]</syntaxhighlight><br />
<br />
===Configure Payload===<br />
<br />
All Cloud API requests must contain the following payload data:<br />
<br />
*''devInfo'': device and app info<br />
*''metadata'': asset metadata<br />
*''event metadata'': type of event<br />
<br />
The payload can be passed through key-values using the Nielsen reserved keys. The specific keys and descriptions are highlighted in the tables included in this section.<br />
<br />
'''Payload Example'''<br />
<br />
The example below should be referenced when following the steps for configuring the request payload.<br />
<br />
<syntaxhighlight lang="javascript"><br />
// 3.1 Configure Payload: devInfo <br />
payload = {<br />
"devInfo": {<br />
"devId": "AD-ID", <br />
"apn": "AppName",<br />
"apv": "1.0",<br />
"uoo": "false"<br />
},<br />
<br />
// 3.2 Configure Payload: metadata<br />
"metadata": {<br />
"static": {}, // object for measuring static content<br />
"content": { // object for measuring video content<br />
"type": "content", // "content" for video<br />
"assetid": "VIDEO-ID123", // unique ID for video<br />
"isfullepisode": "y", // full episode flag<br />
"program": "Program Name", // program name<br />
"title": "Episode Title S3 - EP1", // episode name<br />
"length": "1800", // content duration in seconds<br />
"segB": "Custom Segment B", // custom segment<br />
"segC": "Custom Segment C", // custom segment<br />
"crossId1": "Standard Episode ID", // episode ID<br />
"crossId2": "Content Originator ID", // content orginator (required for distributors)<br />
"airdate": "20161013 20:00:00", // airdate<br />
"adloadtype": "2" //ad load flag<br />
"hasAds": "1", // content contains ads = 1 / no ads = 0<br />
"progen": "CV" // program genre abbreviation<br />
},<br />
"ad": {<br />
"type": "preroll", // type of ad<br />
"assetid": "AD-ID123" // unique ID for ad<br />
}<br />
},<br />
<br />
// 3.3 Configure Payload: events<br />
"event": "playhead", //event name<br />
"position": "300", // position in seconds<br />
"type": "content", //"content" or "ad"<br />
"utc": "1456448742000" //unix timestamp in milliseconds <br />
}<br />
</syntaxhighlight><br />
<br />
=====Configure Payload: devInfo=====<br />
An object <code>"devInfo"</code> will need to be created to capture App and Device information.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| devId || unique ID to identify user (e.g. Advertising ID, Roku Device ID) || custom || Yes<br />
|-<br />
| apn || app name || custom || Yes<br />
|-<br />
| apv || app build version || custom || Yes<br />
|-<br />
| uoo || device opt-out status || <code>"true"</code> or <code>"false"</code> || Yes<br />
|-<br />
|}<br />
<br />
'''Example devInfo Object'''<br />
<syntaxhighlight lang="javascript"><br />
// create devInfo object<br />
"devInfo": {<br />
"devId": "AD-ID",<br />
"apn": "AppName",<br />
"apv": "1.0",<br />
"uoo": "false"<br />
},<br />
</syntaxhighlight><br />
<br />
==== 3.2 Configure Payload: metadata ====<br />
Asset metadata can be passed through <code>"metadata"</code>. There are two asset types: <code>"content"</code> for video and <code>"ad"</code> for ads. The metadata received for each asset is used for classification and reporting.<br />
<br />
You will need to set up <code>"metadata"</code> objects for <code>"content"</code> and <code>"ad"</code> with the required Nielsen keys as shown in the sample code below.<br />
<br />
===== Content Metadata =====<br />
Content metadata should remain constant throughout the entirety of an episode/clip including when ads play.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| type || type of asset || <code>"content"</code> || Yes<br />
|-<br />
| assetid || unique ID assigned to asset || custom || Yes<br />
|-<br />
| program ||name of program (25 character limit) || custom || Yes<br />
|-<br />
| title ||name of program (25 character limit) || custom || Yes<br />
|-<br />
| length || length of content in seconds || <code>seconds</code> (0 for live stream) || Yes<br />
|-<br />
| segB || custom segment B || custom || <br />
|-<br />
| segC || custom segment C || custom || <br />
|-<br />
| airdate || the airdate in the linear TV || YYYYMMDD HH24:MI:SS || Yes<br />
|-<br />
| isfullepisode || full episode flag || <code>"y"</code>- full episode, <code>"n"</code>- non full episode || Yes<br />
|-<br />
| crossId1 || standard episode ID || custom || Yes<br />
|-<br />
| crossId2 || content originator (only required for distributors) || Nielsen ||<br />
|-<br />
| adloadtype || type of ad load:<br />
<code>"1"</code> Linear – matches TV ad load<br />
<br />
<code>"2"</code> Dynamic – Dynamic Ad Insertion (DAI)<br />
|| <code>"2"</code> - DCR measures content with dynamic ads || Yes<br />
|-<br />
| hasAds || ads indicator<br />
<code>"1"</code>: ads included<br />
<br />
<code>"0"</code>: ads not included<br />
|| <code>"1"</code> or <code>"0"</code> || Yes<br />
|-<br />
| progen || Genre (required only for non-TV originated content). See [[DCR OTT Genre List]] for accepted values || <code>"CV"</code> for Comedy Variety || Required for non-TV originated content<br />
|}<br />
<br />
<br />
'''Example Content Object'''<br />
<syntaxhighlight lang='json'>// create content object<br />
"content": {<br />
"type": "content",<br />
"assetid": "VIDEO-ID123",<br />
"isfullepisode": "y",<br />
"program": "Program Name",<br />
"title": "Episode Title S3 - EP1",<br />
"length": "1800",<br />
"segB": "Custom Segment B",<br />
"segC": "Custom Segment C",<br />
"crossId1": "Standard Episode ID",<br />
"crossId2": "Content Originator ID",<br />
"airdate": "20161013 20:00:00",<br />
"adloadtype": "2",<br />
"hasAds": "1", <br />
"progen": "CV"<br />
}</syntaxhighlight><br />
<br />
===== Ad Metadata =====<br />
The ad metadata should be passed for each individual ad.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| type || type of ad || <code>"preroll"</code>, <code>"midroll"</code>, or <code>"postroll"</code> || Yes<br />
|-<br />
| assetid || unique ID assigned to ad || custom || Yes<br />
|}<br />
<br />
===== Example Ad Object =====<br />
<syntaxhighlight lang="javascript"><br />
// create ad object<br />
"ad": {<br />
"type": "preroll",<br />
"assetid": "AD-ID123"<br />
}<br />
</syntaxhighlight><br />
<br />
=== Configure Payload: Events ===<br />
<br />
The last part of the payload is for enabling events so content is measured correctly when viewed. The events and required parameters are included below.<br />
<br />
==== Event Types ====<br />
<br />
The available events are:<br />
{| class="wikitable"<br />
|-<br />
! Event !! Description<br />
|-<br />
| <code>"playhead"</code> || Playhead position in seconds, must be passed as a whole number every 10 seconds. The final playhead position should be sent before an asset has changed to properly capture full duration. When content is paused, stop passing playhead position until content is resumed. On playhead scrubbing, send current playhead position, followed by the playhead position the user scrubs to. For Live streams, you may use Unix Time (in seconds) as the playhead position. Note that ad playheads must also use Unix Time if Unix Time is used for content playheads.<br />
|-<br />
| <code>"complete"</code> || The complete event must be sent when the content has completed full playback. Before calling the complete event, a final playhead update with the final position is required to be sent to receive full duration credit. For Live streams, a complete event must be sent at program boundaries.<br />
|-<br />
| <code>"delete"</code> || The delete event is optional and can be sent when the viewing session is terminated (typically on App close). A new session ID must be generated after sending a delete event. Delete should not be sent on app interruptions or foreground/background events. All creditable duration will be summarized for all asset types when delete occurs (content and ads).<br />
|}<br />
<br />
===== Event Parameters =====<br />
<br />
The following parameters need to be passed when calling events:<br />
<br />
{| class="wikitable"<br />
|-<br />
! Parameter !! Description !! Value !! Required<br />
|-<br />
| <code>"event"</code> || event type || <code>"playhead"</code>, <code>"complete"</code>, or <code>"delete"</code> || Yes<br />
|-<br />
| <code>"position"</code> || playhead position in seconds or Unix time in seconds || <code>"300"</code> || Yes<br />
|-<br />
| <code>"type"</code> || asset type || <code>"content"</code>, <code>"ad"</code> || Yes<br />
|-<br />
| <code>"utc"</code> || Unix timestamp in milliseconds. Must be passed every 10 seconds. || <code>"1472760000000"</code> || Yes<br />
|}<br />
<br />
===== Example Event =====<br />
You can call events by passing values in the required parameters:<br />
<br />
<syntaxhighlight lang="javascript"><br />
"devInfo": [deviceInfo],<br />
"metadata": {<br />
"static": [static metadata],<br />
"content": [content metadata],<br />
"ad": [ad metadata]<br />
},<br />
// Event Parameters<br />
"event": [event], // event name<br />
"position": [playheadPosition], //position in seconds<br />
"type": [asset type], // values are "content" or "ad"<br />
"utc": "1472760000000" //unix timestamp in milliseconds<br />
}<br />
</syntaxhighlight><br />
<br />
'''Note:''' The full payload including "devInfo" and "metadata" must be populated in each event request.<br />
<br />
===== Sample Event Lifecycle =====<br />
The sample event lifecycle can be used as a reference for identifying the order for calling events and values to pass.<br />
<br />
<syntaxhighlight lang="javascript"><br />
// Start of Session: session ID created when App is opened<br />
<br />
// Preroll<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
// Content<br />
Content Playhead {"event": "playhead", "position": "0", "type": "content", "utc": "1472760000000"} <br />
<br />
// Midroll<br />
Midroll Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
// Content resumes at 15 minutes<br />
Content Playhead {"event": "playhead", "position": "900", "type": "content", "utc": "1472760000000"} <br />
<br />
// Content completes at 30 minutes<br />
Complete {"event": "complete", "position": "1800", "type": "content", "utc": "1472760000000"} <br />
<br />
// Postroll<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
//End of Session: The delete event should be called when the App is exited. The values for position and type not required to be passed.<br />
Delete { "event": "delete", "position": "", "type": "", "utc": "1472760000000"} <br />
</syntaxhighlight><br />
<br />
<br />
'''Sample Event Lifecycle - Detailed Storyline'''<br />
This detailed event sequence provides additional insight for the correct events to call when handling certain playback scenarios.<br />
<syntaxhighlight lang='javascript'>// SESSION STARTS<br />
// Start of Session: session ID created when App is opened<br />
<br />
// PREROLL<br />
// Preroll Start - Start each Ad with a position of "0", resetting to '0' for each Ad, and Ad break.<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
// Preroll Stop - End each Ad with the final position of the Ad.<br />
Ad Playhead {"event": "playhead", "position": "15", "type": "ad", "utc": "1472761500000"}<br />
<br />
// CONTENT <br />
// Content Start - Start new content streams with a position of "0" incrementing the position every 10 seconds.<br />
Content Playhead {"event": "playhead", "position": "0", "type": "content", "utc": "1472761500000"} <br />
<br />
// Content Stop Before Ad Break - Send a playhead update including the current content positon before an Ad break.<br />
Content Playhead {"event": "playhead", "position": "299", "type": "content", "utc": "1472787400000"}<br />
<br />
// MIDROLL<br />
// Midroll Start - Start each Ad with a position of "0", resetting to '0' for each Ad, and Ad break.<br />
Midroll Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472787500000"} <br />
<br />
// Midroll Stop - End each Ad with the final position of the Ad.<br />
Ad Playhead {"event": "playhead", "position": "60", "type": "ad", "utc": "1472793500000"}<br />
<br />
// CONTENT<br />
// Content resumes at 5 minutes - Send playhead update with the current resumed position, and begin incrimenting the positon every 10 seconds.<br />
Content Playhead {"event": "playhead", "position": "300", "type": "content", "utc": "1472799500000"} <br />
<br />
// Content completes at 10:12 - Make sure to send in the playhead event with the final content position before sending the complete event.<br />
Final Content Playhead {"event": "playhead", "position": "612", "type": "content", "utc": "1472830700000"} <br />
<br />
Complete {"event": "complete", "position": "612", "type": "content", "utc": "1472830800000"} <br />
<br />
// POSTROLL<br />
// Postroll Start - Start each Ad with a position of "0", resetting to '0' for each Ad, and Ad break.<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472830900000"} <br />
<br />
// Postroll Stop - End each Ad with the final position of the Ad.<br />
Ad Playhead {"event": "playhead", "position": "45", "type": "ad", "utc": "1472835300000"}<br />
<br />
// SESSION ENDS<br />
<br />
//End of Session: The delete event should be called when the App is exited. The values for position and type not required to be passed.<br />
Delete { "event": "delete", "position": "", "type": "", "utc": "1472835400000"} </syntaxhighlight><br />
<br />
<br />
=====Handling Playhead=====<br />
Calling <code>"playhead"</code> is critical for accurate duration crediting. You can reference the below guidance to determine the correct playhead position to pass depending on the playback scenario.<br />
<br />
'''Playhead: General'''<br />
* Playhead position must start at 0 for each new asset, and be passed at least every 10 seconds.<br />
* Final postion must be sent at the end of content or an ad<br />
* Playheads should be sent in seconds only, not milliseconds<br />
<br />
'''Playhead: Ads'''<br />
* The final position must be sent when switching from content to ad, or ad to content.<br />
* Each ad playhead position should be 0 at ad start.<br />
* For Ad Pods, playhead must be called, and reset to 0 for each individual ad. <br />
* The last content position before an Ad should be sent before switching to Ads.<br />
* When content has resumed following an ad break, the playhead position update must continue where the previous content segment left off.<br />
<br />
'''Playhead: User Actions'''<br />
* Upon user scrubbing, the current position must be sent before a user scrubs, and the new position should be sent where the user lands, and begin sending in the 10 second updates thereafter.<br />
* On pause, send the current position and then discontinue sending playhead event updates.<br />
* If a user exits a stream early, the last current position must be sent in a playhead update to receive accurate duration.<br />
<br />
===== Interruption Scenarios =====<br />
<br />
As part of configuring events, you will need to handle all possible interruption scenarios such as:<br />
<br />
*Wi-Fi OFF / ON<br />
*App going Background / Foreground (Video players only, not for Audio players)<br />
*App Crash or Exit<br />
<br />
When playback is interrupted, the app needs to send delete immediately.<br />
<br />
Once playback resumes, a new session will need to be created with a unique session ID. All of the required metadata and events will need to be sent.<br />
<br />
'''Note:''' The session will automatically timeout after 30 minutes of inactivity.<br />
<br />
=== Example Request ===<br />
<br />
Now that we walked through the Cloud API integration steps, your requests should have the following components: Session ID, App ID, and Payload. You can reference the example below when your reviewing your integration.<br />
<br />
<syntaxhighlight lang="javascript"><br />
// 1. Create Session ID<br />
sessionID = dfc7dc6a-66a7-4705-9fba-adaaf7e3d5e0 // Example sessionID created using a UUID Generator<br />
<br />
// 2. Define URL Structure with App ID and Session ID<br />
sessionURL = https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=<br />
<br />
// 3. Configure Payload<br />
// 3.1 Configure Payload: devInfo <br />
payload = {<br />
"devInfo": {<br />
"devId": "AD-ID", <br />
"apn": "AppName",<br />
"apv": "1.0",<br />
"uoo": "false"<br />
},<br />
<br />
// 3.2 Configure Payload: metadata<br />
"metadata": {<br />
"static": {}, // object for measuring static content<br />
"content": { // object for measuring video content<br />
"type": "content", // "content" for video<br />
"assetid": "VIDEO-ID123", // unique ID for video<br />
"isfullepisode": "y", // full episode flag<br />
"program": "Program Name", // program name<br />
"title": "Episode Title S3 - EP1", // episode name<br />
"length": "1800", // content duration in seconds<br />
"segB": "Custom Segment B", // custom segment<br />
"segC": "Custom Segment C", // custom segment<br />
"crossId1": "Standard Episode ID", // episode ID<br />
"crossId2": "Content Originator ID", // content orginator (required for distributors)<br />
"airdate": "20161013 20:00:00", // airdate<br />
"adloadtype": "2", // ad load flag<br />
"hasAds": "1", // content contains ads = 1 / no ads = 0<br />
"progen": "CV" // program genre abbreviation<br />
},<br />
"ad": {<br />
"type": "preroll", // type of ad<br />
"assetid": "AD-ID123" // unique ID for ad<br />
}<br />
},<br />
<br />
// 3.3 Configure Payload: events<br />
"event": "playhead", //event name<br />
"position": "300", // position in seconds<br />
"type": "content", //"content" or "ad"<br />
"utc": "1456448742000" //unix timestamp in milliseconds <br />
}<br />
<br />
// Append payload to URL<br />
var image = new Image()<br />
image.onerror = function() {<br />
// wait and send again<br />
}<br />
(new Image).src = sessionURL+encodeURI(JSON.stringify(payload));<br />
</syntaxhighlight><br />
<br />
==== Enable Debug Logging ====<br />
<br />
Now that you have set up the Cloud API requests, you can enable debug logging to validate your integration. Enabling debug logging is required for Nielsen certification.<br />
<br />
==== GET Request ====<br />
<br />
Display GET Request to console using a name to identify each event (e.g. playhead).<br />
<br />
<syntaxhighlight lang="javascript"><br />
console.log("Event", image); <br />
</syntaxhighlight><br />
<br />
==== Payload ====<br />
<br />
Output payload to identify required metadata and events.<br />
<br />
<syntaxhighlight lang="javascript"><br />
console.log("Event Payload", payload); <br />
</syntaxhighlight><br />
<br />
==== HTTP Response Code ====<br />
<br />
Confirm request was completed by viewing HTTP response code.<br />
<br />
<syntaxhighlight lang="javascript"><br />
code = msg.GetResponseCode();<br />
console.log("Response Code", code); <br />
</syntaxhighlight><br />
<br />
You can reference the HTTP Response Code table when reviewing your requests:<br />
<br />
{| class="wikitable"<br />
|-<br />
! Status Code !! Status Text !! Description<br />
|-<br />
| <code>200</code> || OK || request received<br />
|-<br />
| <code>403</code> || Forbidden || invalid App ID<br />
|-<br />
| <code>404</code> || Not Found || JSON issue<br />
|}<br />
<br />
== Opt-Out ==<br />
Your app must provide a means for the user to Opt-Out, or Opt-In to Nielsen Measurement. This requirement can be fulfilled by checking the device OS for the user's setting of "Limit Ad Tracking" or similar option. If the device offers "Limit Ad Tracking" settings, you should set <code>uoo=true</code> or <code>uoo=false</code> depending on the user's privacy setting. Also, ensure that the <code>devId</code> is set to a blank value if the user elects to opt-out.<br />
<br />
If the device does not have OS-level "Do Not Track" settings, you can implement opt-out by creating an Opt-Out/Opt-In button, toggle switch, or slider within the app "Settings", or "About" section to allow the user selection.<br />
<br />
[[File:Nielsen Opt-Out.png|link=]]<br />
<br />
You will need to store the User Opt-Out (uoo) status, so that it can be retrieved and populated in the <code>"devInfo"</code> metadata which will be sent in every Cloud API event (playhead, complete, & delete). <br />
<br />
{| class="wikitable"<br />
|-<br />
! uoo Key !! Description !! Values<br />
|-<br />
| uoo || Device is Opted-In to Nielsen Measurement (Recommended Default Setting) || <code>"false"</code><br />
|-<br />
| uoo || Device is Opted-Out of Nielsen Measurement || <code>"true"</code><br />
|}<br />
<br />
===== devInfo Opt-In JSON Payload Example =====<br />
<br />
<syntaxhighlight lang="javascript"><br />
"devInfo": {<br />
"apn": "Cloud API Sample App",<br />
"apv": "1",<br />
"devId": "7be25cf9-8c40-5cc2-871e-19bf41940288",<br />
"uoo": "false"<br />
},<br />
</syntaxhighlight><br />
<br />
===== devInfo Opt-Out JSON Payload Example =====<br />
<br />
<syntaxhighlight lang="javascript"><br />
"devInfo": {<br />
"apn": "Cloud API Sample App",<br />
"apv": "1",<br />
"devId": "", //devId must be blank when a user elects to Opt-Out.<br />
"uoo": "true"<br />
},<br />
</syntaxhighlight><br />
<br />
===== Privacy Information Template To Include In Opt-Out Screen =====<br />
<br />
<br />
Additionally, all applications must display the Nielsen privacy policy within their application, typically in the Settings/About screen of your application. The Nielsen privacy policy text is listed below.<br />
<br />
<blockquote><br />
'''''ABOUT NIELSEN MEASUREMENT'''''<br />
<br />
Television and the way we watch it have come a long way since Nielsen began measuring TV audiences in 1950. Today, the ability to watch our favorite shows at any time and on multiple devices amplifies the need for exceptionally adept and flexible audience measurement capabilities.<br />
<br />
Consumers are changing with the times, and the same goes for us. As technology continues to evolve and media companies try new ways to attract viewers, understanding what consumers are watching — and what they're watching on — is more important than ever. Today, viewing video is a personal and mobile experience — anytime and anywhere. Our capabilities provide relevant metrics that are necessary to inform successful marketing and programming and drive continued growth. As a global information and measurement leader, we are committed to protecting the privacy and security of the data we collect, process and use. While our digital measurement products are not used to identify you in any way, they help us and our clients measure and analyze how consumers engage with media across online, mobile and emerging technologies, and offer insights into consumer behavior.<br />
<br />
'''''YOUR CHOICES'''''<br />
<br />
Nielsen believes that you should have a choice about whether to contribute to our research and insights. To opt out or opt in to Nielsen measurement, please toggle your "Limit Ad Tracking" (or similar setting) on your device, or make the appropriate selection within the application's settings. If you have this app on more than one device, you will need to opt out of this app on each device. To learn more about our digital measurement products and your choices in regard to them, please visit http://www.nielsen.com/digitalprivacy.<br />
<br />
'''''Disclosure Template'''''<br />
<br />
Additionally, you must update the App description information from the App Distribution Store with the Nielsen Measurement disclosure below:<br />
<br />
This app features Nielsen's proprietary measurement software which will allow you to contribute to market research, like Nielsen's TV Ratings. Please see http://www.nielsen.com/digitalprivacy for more information.<br />
</blockquote><br />
<br />
== Testing ==<br />
Before providing an app build to Nielsen for testing, it is important to run validation checks once you have enabled debug logging.<br />
<br />
=== Payload Validation ===<br />
<br />
Ensure that all of the required payload data is populating while testing several videos. The following areas are critical to measurement:<br />
*devInfo<br />
*Asset metadata for both content, and ads<br />
*Events<br />
*Opt-Out status<br />
<br />
=== Player Events ===<br />
Review event calls:<br />
<br />
==== playhead ====<br />
*Playhead position updates every 10 seconds starting at position '0' for each new asset.<br />
*Final playhead position is sent on content, or ad before switching between assets.<br />
*Content metadata remains constant throughout an episode, or clip play.<br />
*Ad metadata is populated appropriately for each individual ad.<br />
*Playhead position update resumes for content after an ad break, and resets to 0 for each individual ad.<br />
*For scrubbing, last current position should be sent while scrubbing occurs, and the new position should also be sent where the user scrubs to.<br />
*Exiting a stream early should execute the last current position in a playhead update to receive accurate duration.<br />
*Upon pause, the current position should be sent, and playhead updates should stop incrementing until resume play occurs.<br />
<br />
==== complete ====<br />
*Check that the complete event executes upon content complete after the final playhead update is sent<br />
*Do not execute the complete event for ads<br />
<br />
==== delete ====<br />
*Check to see that the delete event occurs upon app exit, if the platform has the necessary exit callback events.<br />
<br />
==== GET Request Format ====<br />
*Ensure that the event payloads are formatted in JSON<br />
*Check to see that each of the Cloud API GET requests are properly encoded<br />
<br />
==== HTTP Response ====<br />
*Make sure that each of the Cloud API Get requests are received by the Nielsen Cloud API properly through use of the HTTP Response Code outputs enabled in console.<br />
<br />
==== Opt-Out ====<br />
*Test the "uoo" key gets populated accurately for both Opt-In and Opt-Out selections by validating the Cloud API events called after the user Opt-Out/Opt-In selection.<br />
*Test that the devId field is populated with a blank value if a user has elected to Opt-Out. For example: "devId": "",<br />
*If the device supports "Limit Ad Tracking" or has device "Opt-Out" settings, test that uoo=true, and that devId is set to a blank value if enabled in the device settings.<br />
<br />
== Go Live ==<br />
After your integration has been certified, you will need to: Change Endpoint and Disable Logging.<br />
<br />
'''Change Endpoint:''' You will need to update to the production endpoint:<br />
<br />
*Testing: <code>https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/</code><br />
*Production: <code>https://cloudapi.imrworldwide.com/nmapi/v2/</code><br />
<br />
Your production URL structure should now be:<br />
<code>https://cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=[payload]</code><br />
<br />
'''Disable Logging:''' You can now disable debug logging</div>AlexGutierrezhttps://engineeringportal.nielsen.com//w/index.php?title=DTVR_Android_SDK&diff=3617DTVR Android SDK2019-04-29T15:46:15Z<p>AlexGutierrez: </p>
<hr />
<div>{{Breadcrumb|}} {{Breadcrumb|Digital}} {{Breadcrumb|DCR & DTVR}} {{CurrentBreadcrumb}}<br />
[[Category:Digital]]<br />
<br />
== Overview ==<br />
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.<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), and [[Digital Audio]]. Nielsen SDKs are also equipped to measure static content and can track key life cycle events of an application like:<br />
*Application launch events and how long app was running<br />
*Time of viewing a sub section / page in the application.<br />
<br />
If the content being played contains ID3 tags, when played on a mobile device or within a browser, these tags can be sent to Nielsen for collection/processing via the Nielsen SDK. <br />
<blockquote><br />
VOD in TV Ratings (formally knows as Recently Telecast VOD) support is now available; however, you must notify your Nielsen Technical Account Manager to ensure accurate reporting.<br />
</blockquote><br />
<br />
== Prerequisites ==<br />
To start using the App SDK, the following details are required:<br />
* '''App ID (appid):''' Unique ID assigned to the player/site and configured by product.<br />
* '''sfcode:''' Unique identifier for the environment that the SDK should point to.<br />
* '''Nielsen SDK:''' The Nielsen SDK package contains a variety of sample players for your reference.<br />
If you do not have any of these prerequisites or if you have any questions, please contact our SDK sales support team.<br />
Refer to [[Digital Measurement Onboarding]] guide for information on how to get a Nielsen App SDK and appid.<br />
<br />
== Implementation ==<br />
This guide covers implementation steps for iOS using Android Studio utilizing the Standard Nielsen SDK for DTVR.<br />
=== How to obtain the NielsenAppApi ===<br />
The Nielsen AppSDK can either be downloaded directly or can be integrated directly within an application through the use of Gradle. We recommend using the Gradle-based integration whenever possible to ensure you maintain the most recent changes and enhancements to the Nielsen libraries.<br />
* [[Digital_Measurement_Android_Artifactory_Guide|Select to obtain Gradle implementation guide]]<br />
* [[Special:Downloads|Select to Download Directly]]<br />
<br />
== Setting up your Development Environment ==<br />
=== Configuring Android Development Environment ===<br />
*The Nielsen App SDK (located in the [https://engineeringportal.nielsen.com/docs/Special:Downloads Downloads section] of the website) class is the primary application interface to the Nielsen App SDK on Android.<br />
*The Nielsen App SDK class is defined as the only public class belonging to the com.nielsen.app.sdk package.<br />
<br />
'''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.'''<br />
<br />
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.<br />
* '''For Video player applications'''<br />
** The Android OS hosting the App SDK should use a media player supporting HLS streaming (Android 3.0 and later will support it natively).<br />
** 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.<br />
* '''For Audio player applications'''<br />
** 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.<br />
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'''.<br />
Ensure the AppSdk.jar file is added in 'build.grade (App Level) file.<br />
* App SDK 1.2 provides support for x86, mips, and armeabi-7a architecture.<br />
<br />
==== Google Play Services ====<br />
Add the Google Play Services in the project,<br />
Steps: Android Studio -> File -> Project Structure ->(In module selection) select App -> Dependencies (tab) -> Click “+” button and select <code>“com.google.android.gms:play-services”</code>.<br />
Ensure it is added in build.gradle (App level) file<br />
<br />
==== Manifest File ==== <br />
* Add the following permissions on the project’s ''AndroidManifest.xml'' file.<br />
<syntaxhighlight lang="java"><uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" android:required="false" /><br />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/><br />
<uses-permission android:name="android.permission.INTERNET"/></syntaxhighlight><br />
For more details to handle runtime permissions in Android versions, please visit [https://developer.android.com/training/permissions/requesting.html]. <br />
<br />
* In <code>AndroidManifest.xml </code>under <application> node add the following metadata<br />
<br />
<syntaxhighlight lang="java"><meta-data <br />
android:name="com.google.android.gms.version" <br />
android:value="@integer/google_play_services_version"/></syntaxhighlight><br />
<br />
* App SDK checks to see if there is a Google service available and updated.<br />
* 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.<br />
<br />
==== Library ====<br />
Nielsen App SDK uses the following packages/classes from the Google Play service.<br />
* google-play-services_lib<br />
<br />
==== Classes/package ====<br />
* com.google.android.gms.ads.identifier.AdvertisingIdClient;<br />
* com.google.android.gms.ads.identifier.AdvertisingIdClient.Info;<br />
* com.google.android.gms.common.ConnectionResult;<br />
* com.google.android.gms.common.GooglePlayServicesUtil;<br />
* com.google.android.gms.common.GooglePlayServicesRepairableException;<br />
* com.google.android.gms.common.GooglePlayServicesNotAvailableException;<br />
<br />
== SDK Initialization ==<br />
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)<br />
<br />
* A maximum of four SDK instances per appid are supported. <br />
* When four SDK instances exist, you must destroy an old instance before creating a new one.<br />
<br />
The following table contains the list of arguments that can be passed via the AppInfo JSON schema.<br />
<br />
* The appid is provided by the Nielsen Technical Account Manager (TAM). The appid is a GUID data type and is specific to the application.<br />
{| class="wikitable"<br />
|-<br />
! Parameter / Argument !! Description !! Source !! Required? !! Example<br />
|-<br />
| appid || Unique id for the application assigned by Nielsen. It is GUID data type.|| Nielsen-specified || Yes || PXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX<br />
|-<br />
| appname || Name of the application || Client-defined || Optional; automatically detected in SDK 6.0.0.4 and above || Nielsen Sample App<br />
|-<br />
| sfcode || Nielsen collection facility to which the SDK should connect.<br />
|| Nielsen-specified || Yes || us<br />
|-<br />
|containerID || View ID of the UI element used as player view in application for Viewability ||Client-defined||Optional||"1234567"<br />
|-<br />
| nol_devDebug || Enables Nielsen console logging. Only required for testing<br />
|| Nielsen-specified || Optional || "DEBUG"<br />
|}<br />
<br />
==== Debug flag for development environment ====<br />
Player application developers / integrators can use Debug flag to check whether an App SDK API call made is successful. To activate the Debug flag,<br />
Pass the argument <code>@"nol_devDebug":@"INFO"</code>, in the JSON string . The permitted values are:<br />
<br />
* '''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.<br />
* '''WARN''': Indicates potential integration / configuration errors or SDK issues.<br />
* '''ERROR''': Indicates important integration errors or non-recoverable SDK issues.<br />
* '''DEBUG''': Debug logs, used by the developers to debug more complex issues.<br />
<br />
Once the flag is active, it logs each API call made and the data passed. The log created by this flag is minimal.<br />
<blockquote>'''Note''': DO NOT activate the Debug flag in a production environment.</blockquote><br />
<br />
=== Sample SDK Initialization Code ===<br />
[[AppSDK()]] is no longer a singleton object and should be initialized as below.<br />
<br />
'''Initialization of App SDK object through a JSON object'''<br />
<syntaxhighlight lang="java"> <br />
try<br />
{<br />
// Prepare AppSdk configuration object (JSONObject)<br />
JSONObject appSdkConfig = new JSONObject()<br />
.put("appid", "PDA7D5EE6-B1B8-XXXX-XXXX-2A788BCXXXCA")<br />
.put("sfcode", "us")<br />
.put("containerID": "2131558561")<br />
.put("nol_devDebug", "DEBUG"); // only for debug builds<br />
<br />
// Pass appSdkConfig to the AppSdk constructor<br />
mAppSdk = new AppSdk(appContext, appSdkConfig, appSdkListener);<br />
}<br />
catch (JSONException e)<br />
{<br />
Log.e(TAG, "Couldn’t prepare JSONObject for appSdkConfig", e);<br />
}<br />
</syntaxhighlight><br />
Here, <code>appContext</code> is the App context object and <code>appSdkConfig</code> is JSON object for holding the parameters (<code>appid</code>, <code>sfcode</code>) the App passes to the Nielsen App SDK via a JSON string. The appid is obtained from Nielsen operational support and is unique to the app.<br />
<br />
<br />
*The integration of Nielsen App SDK will depend on type of client app.<br />
*Ensure that SDK files (AppSdk.jar and libAppSdk.so [App SDK 1.2 Only]) are included under the App’s project and the App SDK is linked to the App (the setting to link App SDK to the App can be found on property page of the App’s project).<br />
<!--<br />
== Initializing the Nielsen AppSDK to measure the Viewability ==<br />
The integrator to support the viewability metrics in the application has to provide a tag value of the player view to let Nielsen AppSDK know that there is a player that needs to be tracked. It’s called the ‘containerId’ and it should be passed in application info dictionary as string while initializing the Nielsen AppSDK.<br />
<br />
==== Android ====<br />
{| class="wikitable"<br />
|-<br />
! # !! Parameter Name !! Description !! Supported Values !! Example<br />
|-<br />
| 1 || containerId ||View ID of the UI element used as player view in application. getId() method of View class can be used to get this value. ||A positive integer used to identify the view. || 2131558561<br />
|}<br />
<br />
==== iOS ====<br />
{| class="wikitable"<br />
|-<br />
! # !! Parameter Name !! Description !! Supported Values !! Example<br />
|-<br />
| 1 || containerId ||The tag of the UIView that represents the Player View ||The string value representing the NSInteger value with maximum value of NSIntegerMax that is related on 32- or 64-bit applications. || "100" <br> "2131558561"<br />
|}<br />
<br />
For iOS it is required to link additional frameworks that are needed for viewability engine:<br><br />
<code>JavaScriptCore.framework</code> <br><br />
<code>WebKit.framework</code><br />
<br />
The Nielsen AppSDK uses a tracking WebView (TWV) approach. For more information on Viewability, please refer to [https://engineeringportal.nielsen.com/docs/Implementing_Viewability_with_AppSDK Implementing Viewability with AppSDK.]<br />
--><br />
== APP SDK Error & Event Codes ==<br />
To view the Error and Event codes for iOS and Android, please review the [[APP SDK Event Codes|App SDK Event Code]] Reference page.<br />
<br />
== Content Metadata and SDK Events ==<br />
=== Content Metadata ===<br />
Content metadata should remain constant throughout the completion of an episode or live stream.<br />
{| class="wikitable"<br />
|-<br />
! Key !! Description !! Values !! Required<br />
|-<br />
| channelName || Any string representing the channel/stream || 32-character free-form text || ✓<br />
|-<br />
| type || type of asset || "content" || ✓<br />
|-<br />
| adModel || linear vs dynamic ad model || 1 = Linear matches TV ad load || ✓<br />
|-<br />
|}<br />
<syntaxhighlight lang="java"> <br />
//Loading Channel Info.<br />
public JSONObject loadChannelInfo(){<br />
JSONObject channel = null;<br />
try {<br />
channel = new JSONObject()<br />
.put("channelName", "ChannelTitle");<br />
} catch (JSONException e) {<br />
e.printStackTrace();<br />
}<br />
return channel;<br />
}<br />
</syntaxhighlight><br />
------------------------<br />
<syntaxhighlight lang="java"> <br />
public JSONObject loadDtvr() {<br />
try {<br />
dtvr = new JSONObject()<br />
.put( "type","content")<br />
.put("adModel", "1");<br />
}<br />
return dtvr;<br />
}<br />
</syntaxhighlight><br />
<br />
=== SDK Events ===<br />
[[File:appsdkTimeline-DTVR-V2.png|icon]]<br />
{| class="wikitable"<br />
|-<br />
! Event !! Parameter !! Description<br />
|-<br />
| 'loadMetadata' || content/ad metadata object || Needs to be called at the beginning of each asset to pass type, channelName, and adModel.<br />
|-<br />
|'play'||program or feed name||Call when starting or resuming a streaming session.<br />
|-<br />
| 'sendID3' || Used to send the ID3 tag payload retrieved from the stream || Needs to be called at the beginning of playback<br />
||<br />
|-<br />
| 'end' || Content end || Call when the current video asset completes playback or when a stream is interrupted. <br/><br />
Example: At the end of the content stream, if the user switches to another piece of content, when the browser is refreshed or closed.<br />
|}<br />
<br />
=== Configure API calls - play ===<br />
<syntaxhighlight lang="java"> appSdk.play(sdkMethods.loadChannelInfo());</syntaxhighlight><br />
<br />
=== Configure API calls - loadMetadata ===<br />
Use <code>loadMetadata</code> to pass ‘content’ and ‘ad’ <code>Digital Measurement Metadata</code>. The CMS data must be passed as a JSON object.<br />
<syntaxhighlight lang="java"> appSdk.loadMetadata(data);</syntaxhighlight><br />
<br />
=== Configure API calls - sendID3 ===<br />
[[sendID3]] API is a receiver for timed metadata events (ID3 tags) provided through iOS’s NSNotificationCenter notification system. This API filters out Nielsen-specific ID3 tags from the system and buffers the data for transfer to Nielsen’s collection facility.<br />
<syntaxhighlight lang="java"> //Sending ID3 tag to SDK.<br />
appSdk.sendID3(id3String);</syntaxhighlight><br />
'''Sample ID3 tags''' <br />
* <code>www.nielsen.com/X100zdCIGeIlgZnkYj6UvQ==/X100zdCIGeIlgZnkYj6UvQ==/AAAB2Jz2_k74GXSzx4npHuI_<wbr />JwJd3QSUpW30rDkGTcbHEzIMWleCzM-uvNOP9fzJcQMWQLJqzXMCAxParOb5sGijSV9dNM3QiBniJYGZ5GI-lL1fXTTN0IgZ4iWBmeRiPpS9AAAAAAAAAAAAAAAAAAAAAFJWFM5SVhTONNU=/00000/00000/00</code><br />
* <code>www.nielsen.com/X100zdCIGeIlgZnkYj6UvQ==/R8WHe7pEBeqBhu8jTeXydg==/AAICoyitYqlxT7n6aZ0oMCGhe<wbr />Fi4CXFp46AMUPZz1lMr_M9tr3_cjee1SHqxrOiVerMDLeyn9xzocZSKwi746Re8vNOtpNCAZjYABs_J0R25IHpvOc1HS8<wbr />QHGgD5TgOJeS6gX100zdCIGeIlgZnkYj6UvVJWFNhSVhTiPE0=/00000/46016/00</code><br />
Refer to [[iOS SDK API Reference#Retrieving ID3 Tags|Retrieving ID3 Tags]] section to know more details.<br />
<br />
===Configure API calls - stop ===<br />
Call <code>stop</code> in case of interruptions during playback like flight mode, Wi-Fi toggle, etc. Call <code>play</code> when resuming the stream / starting the new stream.<br />
<syntaxhighlight lang="java">[nielsenApi stop];</syntaxhighlight><br />
<br />
===Configure API calls - end ===<br />
Call [[end]] only at the end of playback.<br />
<syntaxhighlight lang="java">[nielsenApi end];</syntaxhighlight><br />
<br />
== Retrieving ID3 Tags ==<br />
ID3 tags have a payload of about 249 characters and start with "www.nielsen.com".<br />
<br />
ID3 tags are extracted by observing a property called timedMetadata on the iOS player item. Now this is done via a concept called KVO (Key Value Observing), where you register interest in a property, and the runtime will let you know when it has changed.<br />
<br />
Both the iOS native players have the ability to extract ID3 tags, If any other player apart from iOS native players (AVPlayer, MPMoviePlayer) is used, check and ensure that the player has the capability to extract ID3 tags.<br />
<br />
=== Examples of extracting ID3 tags fromAndroid Native Media Player ===<br />
As the Android Media Player versions (prior to Android 6 / Android API 23) do not support ID3, Nielsen has created a library that becomes an extension to the media player, thus MPX. This library extracts the ID3 tags and sends them to the app. For more information on how to use the MPX component, refer to the Nielsen-supplied sample application.<br />
<br />
Starting from '''Android 6 (Android API 23)''', Android Native Media Player allows apps to register a callback to be invoked, when a selected track has the timed metadata available. Currently, only HTTP Live Streaming (HLS) data URI’s embedded with timed ID3 tags generate TimedMetadata. Once the HLS video starts, call onTimedMetaDataAvailable() as and when the player observes a TimedMetadata (ID3 tag).<br />
<br />
<syntaxhighlight lang="java">@Override<br />
public void onTimedMetaDataAvailable(MediaPlayer mediaPlayer, TimedMetaData timedMetaData) {<br />
<br />
if(timedMetaData != null && timedMetaData.getMetaData() != null && mMediaPlayer.isPlaying()){<br />
<br />
//getting metadata.<br />
String iD3Payload = new String(timedMetaData.getMetaData(), StandardCharsets.UTF_8);<br />
<br />
//If tag metadata contains "www.nielsen.com", then only sending to SDK<br />
if (null != iD3Payload && iD3Payload.contains("www.nielsen.com"))<br />
{<br />
//getting index<br />
int index = iD3Payload.indexOf("www.nielsen.com");<br />
<br />
//getting substring as ID3 tag will be of 249 characters<br />
String id3String = iD3Payload.substring(index, (index + 249));<br />
Log.d(TAG, "TimedMetaData ID3 Tag:" + id3String);<br />
<br />
//Sending ID3 tag to SDK.<br />
appSdk.sendID3(id3String);<br />
}<br />
}<br />
}<br />
<br />
}</syntaxhighlight><br />
<br />
== Life cycle of SDK instance ==<br />
Life cycle of SDK instance includes four general states:<br />
# '''Initial state''' – The SDK is not initialized and hence, not ready to process playing information. Once the SDK is moved out of this state, it needs instantiation of the new SDK instance in order to get the instance in the '''Initial state'''.<br />
# '''Idle state''' – The SDK is initialized and is ready to process playing information. Once Initialized, the SDK instance is not processing any data, but is listening for the play event to occur.<br />
# '''Processing state''' – The SDK instance is processing playing information. The <code>'''play'''</code> and <code>'''loadMetadata''' </code> calls move the SDK instance into this state. In this state, the SDK instance will be able to process the following calls.<br />
## <code>'''stop'''</code> – Call this API when the playback is paused, switches between content and ad (within the same content playback) or encounters interruptions.<br />
## <code>'''end'''</code> – SDK instance exits from Processing state when this API is called.<br />
# '''Disabled state''' – The SDK instance is disabled and is not processing playing information. SDK instance moves into this state in one of the following scenarios.<br />
## Initialization fails<br />
## <code>'''appDisableApi'''</code> is set to <code>true</code> ''(This is testing purposes only. Not for User Opt-Out.)''<br />
<br />
<blockquote>'''Note:''' For API Version 5.1 and above, App SDK will fire data pings and continue measurement even after the user has opted out from Nielsen measurement on a device. The data ping will be marked as opted-out ping.<br />
<br />
'''Note''': In case of any interruptions during playback due to alarm, calendar, call, flight mode, Wi-Fi toggle, channel change, etc., call </code>stop</code> to stop the measurement.<br />
<br />
== Handling Foreground and Background states ==<br />
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<br />
* Enable the Nielsen SDK to measure background/foreground state by makingthe relevant update to the AndroidManifest.<br />
* Integrate Nielsen’s SdkBgFgDetectionUtility class within your Custom Application Class.<br />
* Custom implementation of the required methods within your application.<br />
<br />
=== ForeGround/Background Measurement via AndroidManifest ===<br />
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.<br />
<syntaxhighlight lang="java"><br />
<application android:name="com.nielsen.app.sdk.AppSdkApplication"><br />
</syntaxhighlight><br />
<br />
=== Using the Android SdkBgFbDetectionUtility Class ===<br />
For developers who are already using the application class, it is recommended that background/foreground state is implemented using the [https://engineeringportal.nielsen.com/docs/Android_Background_Foreground SdkBgFgDetectionUtility class]. The [https://engineeringportal.nielsen.com/docs/Android_Background_Foreground SdkBgFgDetectionUtility class] is compatible with Android 4+ and has been made available to Nielsen clients.<br />
<br />
=== Manual Background/ForeGround State Management ===<br />
In cases where the developer is not able to use the AndroidManifest.xml solution nor the Nielsen provided [https://engineeringportal.nielsen.com/docs/Android_Background_Foreground 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.<br />
<br />
The SDK is informed about app state using the below methods.<br />
<syntaxhighlight lang="java"><br />
AppLaunchMeasurementManager.appInForeground(getApplicationContext());<br />
AppLaunchMeasurementManager.appInBackground(getApplicationContext());<br />
</syntaxhighlight><br />
Within the lifecycle of individual activities, onResume() and onPause() are best suited to providing indication of the app state.<br />
<br />
<br />
Correct measurement of the foreground/background state is crucial to Static App measurement within Nielsen Digital Content Ratings (DCR).<br />
<br />
== Interruptions during playback ==<br />
As part of integrating Nielsen App SDK with the player application, the Audio / Video app developer needs to handle the following possible interruption scenarios:<br />
* Pause / Play<br />
* Network Loss (Wi-Fi / Airplane / Cellular)<br />
* Call Interrupt (SIM or Third party Skype / Hangout call)<br />
* Alarm Interrupt<br />
* Content Buffering<br />
* Device Lock / Unlock (Video players only, not for Audio players)<br />
* App going in the Background/Foreground (Video players only, not for Audio players)<br />
* Channel / Station Change Scenario<br />
* Unplugging of headphone<br />
In case of encountering one of the above interruptions, the player application needs to<br />
* Call [[stop]] immediately (except when content is buffering) and withhold sending playhead position.<br />
* Call <code>play</code> once the playback resumes.<br />
<br />
== Pre-Certification Checklists ==<br />
After the application is ready to be sent for Nielsen Certification, please go through the [[Digital Pre-Certification Checklist App SDK]] and ensure the app behaves as expected, before submitting to Nielsen.<br />
<br />
== Privacy and Opt-Out ==<br />
There are two primary methods for implementing user Opt-out preferences:<br />
# '''[[#OS-level_Opt-out|OS-level Opt-out]]''' - managed by ''Opt out of Ads Personalization'' setting on device ('''preferred approach''').<br />
# '''[[#Legacy_Opt-out|Legacy Opt-out]]''' - Direct call to SDK; used only for older versions of Nielsen Android SDK versions (< 5.1.1.18)<br />
<br />
=== OS-level Opt-out ===<br />
''OS-level Opt-out'' method available on Nielsen Android '''SDK Versions 5.1.1.18 and above'''.<br />
<br />
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.<br />
<br />
=== Legacy Opt-out ===<br />
The ''Legacy opt-out'' method is only necessary for Nielsen Android '''SDK versions less than 5.1.1.18'''.<br />
<br />
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.<br />
<br />
==== The legacy opt-out method works as follows: ====<br />
* Get the current Nielsen opt-out URL via [[userOptOutURLString()]]<br />
* Display a WebView element whose loadUrl is set to the value obtained from [[userOptOutURLString()]]<br />
* Detect if the WebView URL changes to a special URL that indicates Opt-in, or Opt-out and close the WebView<br />
** Opt-out if the WebView URL = <code>nielsenappsdk://1</code><br />
** Opt-in if the WebView URL = <code>nielsenappsdk://0</code><br />
* Pass the detected URL to the [[userOptOut()]] function<br />
** Example: <syntaxhighlight lang=java>appSdk.userOptOut("nielsenappsdk://1"); // User opt-out</syntaxhighlight><br />
<br />
==== Legacy Opt Out example code ====<br />
<syntaxhighlight lang="java"><br />
public class OptOutActivity extends AppCompatActivity implements IAppNotifier {<br />
<br />
WebView webView;<br />
AppSdk appSdk;<br />
<br />
private static final String NIELSEN_URL_OPT_OUT = "nielsenappsdk://1";<br />
private static final String NIELSEN_URL_OPT_IN = "nielsenappsdk://0";<br />
<br />
// Within your app you would provide your User the option to Opt Out.<br />
// Perhaps via a toggle or button<br />
// This is separate from Limit Ad Tracking <br />
<br />
let urlStr = navigationAction.request.url?.absoluteString<br />
<br />
if(urlStr == NIELSEN_URL_OPT_OUT || urlStr == NIELSEN_URL_OPT_IN){<br />
let appApi = self.nielsenApi<br />
appApi?.userOptOut(urlStr)<br />
}<br />
</syntaxhighlight><br />
<br />
=== 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 [[getOptOutStatus()]] property in the Nielsen Android SDK API,<br />
<br />
=== Required Privacy Links ===<br />
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.<br />
<br />
In addition, the following text must be included in your app store description.<br />
<blockquote><br />
'''"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><br />
==== Webview Example ====<br />
The below code is an example of displaying the Nielsen Privacy page to the user.<br />
<syntaxhighlight lang="java"><br />
public class OptOutActivity extends AppCompatActivity implements IAppNotifier {<br />
<br />
WebView webView;<br />
AppSdk appSdk;<br />
<br />
@Override<br />
public void onCreate(@Nullable Bundle savedInstanceState) {<br />
super.onCreate(savedInstanceState);<br />
setContentView(R.layout.activity_optout);<br />
webView = (WebView) findViewById(R.id.webView);<br />
<br />
webView.getSettings().setJavaScriptEnabled(true);<br />
<br />
webView.setWebViewClient(new WebViewClient() {<br />
@SuppressWarnings("deprecation")<br />
@Override<br />
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {<br />
Toast.makeText(OptOutActivity.this, description, Toast.LENGTH_SHORT).show();<br />
}<br />
@TargetApi(android.os.Build.VERSION_CODES.M)<br />
@Override<br />
public void onReceivedError(WebView view, WebResourceRequest req, WebResourceError rerr) {<br />
// Redirect to deprecated method, so you can use it in all SDK versions<br />
onReceivedError(view, rerr.getErrorCode(), rerr.getDescription().toString(), req.getUrl().toString());<br />
}<br />
});<br />
<br />
NielsenInit nielsenInit = new NielsenInit();<br />
appSdk = nielsenInit.initAppSdk(getApplicationContext(), this);<br />
//Getting the optPut URL from eventTracker<br />
String url = appSdk.userOptOutURLString();<br />
webView.loadUrl(url);<br />
}<br />
</syntaxhighlight><br />
<br><br />
<br />
== Going Live ==<br />
Following Nielsen testing, users need to make one update to the initialization call to ensure that the site is being measured properly.<br />
<br />
# '''Debug Logging''': Disable logging by deleting <code>{nol_sdkDebug: 'DEBUG'}</code> from initialization call.<br />
<br />
'''Note''': before going live you have to inform Nielsen team - this is necessary, because Nielsen team has to adjust internal configuration parameter to enable data collection. Without that notification no data will be collected and no data will be reported.<br />
<br />
== Sample Applications ==<br />
The below sample applications have been designed to show the API's functionality and are broken into two distinct categories:<br />
* '''Basic''' - To show the functionality of the Nielsen API using a standard no-frills player.<br />
** [[Swift Basic Sample|Swift 4.0 Sample]]<br />
** [[Objective-c Basic example|Objective-C Sample]]<br />
** [[Android Basic example|Android Studio Example]]<br />
<br />
* '''Advanced''' - Nielsen API integrated into a custom video player is contained in the ZIP package.</div>AlexGutierrezhttps://engineeringportal.nielsen.com//w/index.php?title=DTVR_iOS_SDK&diff=3616DTVR iOS SDK2019-04-29T15:45:42Z<p>AlexGutierrez: Changes to Artifactory description to encourage use to dependency manager implementation</p>
<hr />
<div>{{Breadcrumb|}} {{Breadcrumb|Digital}} {{Breadcrumb|DCR & DTVR}} {{CurrentBreadcrumb}}<br />
[[Category:Digital]]<br />
<br />
== Overview ==<br />
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.<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), and [[Digital Audio]]. Nielsen SDKs are also equipped to measure static content and can track key life cycle events of an application like:<br />
*Application launch events and how long app was running<br />
*Time of viewing a sub section / page in the application.<br />
<br />
If the content being played contains ID3 tags, when played on a mobile device or within a browser, these tags can be sent to Nielsen for collection/processing via the Nielsen SDK. <br />
<blockquote><br />
VOD in TV Ratings (formally knows as Recently Telecast VOD) support is now available; however, you must notify your Nielsen Technical Account Manager to ensure accurate reporting.<br />
</blockquote><br />
<br />
== Prerequisites ==<br />
To start using the App SDK, the following details are required:<br />
* '''App ID (appid):''' Unique ID assigned to the player/site and configured by product.<br />
* '''sfcode:''' Unique identifier for the environment that the SDK should point to.<br />
* '''Nielsen SDK:''' The Nielsen SDK package contains a variety of sample players for your reference.<br />
If you do not have any of these prerequisites or if you have any questions, please contact our SDK sales support team.<br />
Refer to [[Digital Measurement Onboarding]] guide for information on how to get a Nielsen App SDK and appid.<br />
<br />
== Implementation ==<br />
This guide covers implementation steps for iOS using Xcode utilizing the Standard Nielsen SDK for DTVR.<br />
<br />
== Setting up your Development Environment ==<br />
Prior to SDK Version 6.2.0.0 the IOS framework has been distributed as a static library packaged into framework bundle format. Apple recommends to use dynamic framework, it has some benefits over static libraries like less executable file size of an app, faster startup time and native support in xCode IDE. Nielsen AppSDK has been transformed into dynamic framework in this release ([[iOS_SDK_API_Reference|static framework]] is still available).<br />
<br />
If migrating from the static library to this new dynamic framework, once implemented, unless your specific application requires, you can remove the following Frameworks that were once required:<code> [AdSupport, JavascriptCore, SystemConfiguration, Security, AVFoundation, libc++] </code><br />
<br />
=== How to obtain the NielsenAppApi.Framework ===<br />
The Nielsen AppSDK can either be downloaded directly or can be integrated directly within an application through the use of CocoaPods. We recommend using the CocoaPods-based integration whenever possible to ensure you maintain the most recent changes and enhancements to the Nielsen libraries.<br />
* [[Digital_Measurement_iOS_Artifactory_Guide|Select to obtain Cocoapod implementation guide]]<br />
* [[Special:Downloads|Select to Download Directly]]<br />
<br />
=== Configuring Xcode Development Environment ===<br />
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<br />
<br />
<blockquote>'''Note''': All communications between the SDK and the Census (Collection Facility) use HTTPS.</blockquote><br />
<br />
=== Download Framework ===<br />
The first step is to download and copy the [[Special:Downloads|NielsenAppApi.framework]] bundle to the app project directory.<br />
=== Add Framework ===<br />
In the General tab for app configuration add NielsenAppApi.framework in the list of Embedded Binaries.<br />
=== Add Path ===<br />
Add path to the NielsenAppApi.framework in the Framework Search Paths build setting.<br />
=== Import Framework ===<br />
Add NielsenAppApi.framework module in the source file of your app:<br />
<br />
==== Using Swift ====<br />
To import a set of Objective-C files in the same app target as your Swift code, you rely on an Objective-C bridging header to expose those files to Swift. Xcode offers to create this header file when you add a Swift file to an existing Objective-C app, or an Objective-C file to an existing Swift app.<br />
*Select File/New File/Objective-C File<br />
*Xcode will prompt you to create a bridging header.<br />
[[File:bridgingheader 2x.png|600px|center|link=]]<br />
Once this file has been created, you need to add the following:<br />
<syntaxhighlight lang="swift"><br />
#import <NielsenAppApi/NielsenAppApi.h><br />
</syntaxhighlight><br />
<br />
==== Using Objective-C ====<br />
Add the code to the View Controller’s header file.<br />
<syntaxhighlight lang ="objective-c"><br />
#import <NielsenAppApi/NielsenAppApi.h><br />
</syntaxhighlight><br />
<br />
== SDK Initialization ==<br />
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)<br />
<br />
* A maximum of four SDK instances per appid are supported. <br />
* When four SDK instances exist, you must destroy an old instance before creating a new one.<br />
<br />
The following table contains the list of arguments that can be passed via the AppInfo JSON schema.<br />
<br />
* The appid is provided by the Nielsen Technical Account Manager (TAM). The appid is a GUID data type and is specific to the application.<br />
{| class="wikitable"<br />
|-<br />
! Parameter / Argument !! Description !! Source !! Required? !! Example<br />
|-<br />
| appid || Unique id for the application assigned by Nielsen. It is GUID data type.|| Nielsen-specified || Yes || PXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX<br />
|-<br />
| appname || Name of the application || Client-defined || Optional; automatically detected in SDK 6.0.0.4 and above || Nielsen Sample App<br />
|-<br />
| sfcode || Nielsen collection facility to which the SDK should connect.<br />
|| Nielsen-specified || Yes || us<br />
|-<br />
|containerID || View ID of the UI element used as player view in application for Viewability ||Client-defined||Optional||"1234567"<br />
|-<br />
| nol_devDebug || Enables Nielsen console logging. Only required for testing<br />
|| Nielsen-specified || Optional || "DEBUG"<br />
|}<br />
<br />
==== Debug flag for development environment ====<br />
Player application developers / integrators can use Debug flag to check whether an App SDK API call made is successful. To activate the Debug flag,<br />
Pass the argument <code>@"nol_devDebug":@"INFO"</code>, in the JSON string . The permitted values are:<br />
<br />
* '''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.<br />
* '''WARN''': Indicates potential integration / configuration errors or SDK issues.<br />
* '''ERROR''': Indicates important integration errors or non-recoverable SDK issues.<br />
* '''DEBUG''': Debug logs, used by the developers to debug more complex issues.<br />
<br />
Once the flag is active, it logs each API call made and the data passed. The log created by this flag is minimal.<br />
<blockquote>'''Note''': DO NOT activate the Debug flag in a production environment.</blockquote><br />
<br />
=== Sample SDK Initialization Code ===<br />
{{ExampleCode|<br />
|Swift = <br />
Swift 4.0 Example:<br />
<code>NielsenInit.swift</code><br />
<syntaxhighlight lang="swift"><br />
import Foundation<br />
import NielsenAppApi<br />
<br />
class NielsenInit : NSObject {<br />
class func createNielsenApi(delegate: NielsenAppApiDelegate) -> NielsenAppApi?{<br />
<br />
let appInformation:[String: String] = [<br />
<br />
"appid": "PDA7D5EE6-B1B8-4123-9277-2A788XXXXXXX",<br />
"sfcode": "us",<br />
"nol_devDebug": "DEBUG"<br />
"containerId": String(containerId) //Keep container id unique constant, you can use tag property of player.<br />
]<br />
<br />
return NielsenAppApi(appInfo:appInformation, delegate:delegate)<br />
}<br />
}<br />
</syntaxhighlight><br />
<br />
<br />
Sample code using AVPlayer.<br />
<code>ViewController.swift</code><br />
<br />
<syntaxhighlight lang="swift"><br />
class ViewController: UIViewController, NielsenAppApiDelegate, AVPlayerViewControllerDelegate {<br />
<br />
// your code// <br />
<br />
override func viewDidLoad() {<br />
super.viewDidLoad()<br />
<br />
//Getting the instance of NielsenApi<br />
self.nielsenApi = NielsenInit.createNielsenApi(delegate: self)<br />
<br />
}<br />
}<br />
</syntaxhighlight><br />
|Objective C = <br />
Initialize the Nielsen App object within the viewDidLoad view controller delegate method using initWithAppInfo:delegate:<br />
<blockquote>If App SDK is initialized using init or new methods, it will ignore the API calls resulting in no measurement. The SDK will not return any errors.</blockquote><br />
<syntaxhighlight lang="objective-c"> <br />
#import "NielsenInit.h"<br />
#import <NielsenAppApi/NielsenEventTracker.h><br />
<br />
@implementation NielsenInit<br />
<br />
+ (NielsenEventTracker *)createNielsenEventTrackerWithDelegate:(id<NielsenEventTrackerDelegate>)delegate<br />
{<br />
//Initialising the NielsenEventTracker class by passing app information which returns the instance of NielsenEventTracker.<br />
<br />
NSDictionary *appInformation = @{ @"appid": @"PDA7D5EE6-B1B8-4123-9277-2A788XXXXXXX",<br />
@"appversion": @"1.0",<br />
@"sfcode": @"us",<br />
@"nol_devDebug": @"DEBUG",<br />
@"containerId": @"1" };<br />
<br />
return [[NielsenEventTracker alloc] initWithAppInfo:appInformation delegate:delegate];<br />
}<br />
<br />
@end<br />
</syntaxhighlight><br />
<br />
<br />
The following would be the <code>NielsenInit.h</code> file:<br />
<syntaxhighlight lang="objective-c"><br />
<br />
#import <Foundation/Foundation.h><br />
<br />
@class NielsenEventTracker;<br />
@protocol NielsenEventTrackerDelegate;<br />
<br />
@interface NielsenInit : NSObject<br />
<br />
+ (NielsenEventTracker *)createNielsenEventTrackerWithDelegate:(id<NielsenEventTrackerDelegate>)delegate;<br />
<br />
@end<br />
</syntaxhighlight><br />
<br />
}}<br />
<br />
<!-- == Initializing the Nielsen AppSDK to measure the Viewability ==<br />
The integrator to support the viewability metrics in the application has to provide a tag value of the player view to let Nielsen AppSDK know that there is a player that needs to be tracked. It’s called the ‘containerId’ and it should be passed in application info dictionary as string while initializing the Nielsen AppSDK.<br />
<br />
==== Android ====<br />
{| class="wikitable"<br />
|-<br />
! # !! Parameter Name !! Description !! Supported Values !! Example<br />
|-<br />
| 1 || containerId ||View ID of the UI element used as player view in application. getId() method of View class can be used to get this value. ||A positive integer used to identify the view. || 2131558561<br />
|}<br />
<br />
==== iOS ====<br />
{| class="wikitable"<br />
|-<br />
! # !! Parameter Name !! Description !! Supported Values !! Example<br />
|-<br />
| 1 || containerId ||The tag of the UIView that represents the Player View ||The string value representing the NSInteger value with maximum value of NSIntegerMax that is related on 32- or 64-bit applications. || "100" <br> "2131558561"<br />
|}<br />
<br />
For iOS it is required to link additional frameworks that are needed for viewability engine:<br><br />
<code>JavaScriptCore.framework</code> <br><br />
<code>WebKit.framework</code><br />
<br />
The Nielsen AppSDK uses a tracking WebView (TWV) approach. For more information on Viewability, please refer to [https://engineeringportal.nielsen.com/docs/Implementing_Viewability_with_AppSDK Implementing Viewability with AppSDK.]<br />
--><br />
<br />
== APP SDK Error & Event Codes ==<br />
To view the Error and Event codes for iOS and Android, please review the [[APP SDK Event Codes|App SDK Event Code]] Reference page.<br />
<br />
== Content Metadata and SDK Events ==<br />
=== Content Metadata ===<br />
Content metadata should remain constant throughout the completion of an episode or live stream.<br />
{| class="wikitable"<br />
|-<br />
! Key !! Description !! Values !! Required<br />
|-<br />
| channelName || Any string representing the channel/stream || 32-character free-form text || <br />
|-<br />
| type || type of asset || "content" || ✓<br />
|-<br />
| adModel || linear vs dynamic ad model || 1 = Linear matches TV ad load || ✓<br />
|-<br />
|}<br />
<syntaxhighlight lang="objectivec"> <br />
- (NSDictionary *)loadChannelInfo<br />
{<br />
//Loading Channel Info. <br />
NSString *strUrl = self.url.absoluteString; <br />
NSDictionary *channel = @{ @"channelName" : @"TheGreatBigMovie": strUrl }; <br />
return channel;<br />
}<br />
- (NSDictionary *)loadDtvr{<br />
<br />
//Loading DTVR data <br />
NSDictionary *dtvr = @{ @"adModel":@"1" , <br />
@"type":@"content",}; <br />
return dtvr;<br />
}<br />
</syntaxhighlight><br />
<br />
=== SDK Events ===<br />
[[File:appsdkTimeline-DTVR-V2.png|icon]]<br />
{| class="wikitable"<br />
|-<br />
! Event !! Parameter !! Description<br />
|-<br />
| 'loadMetadata' || content/ad metadata object || Needs to be called at the beginning of each asset to pass type, channelName, and adModel.<br />
|-<br />
|'play'||program or feed name||Call when starting or resuming a streaming session.<br />
|-<br />
| 'stop' || playhead position || Call when content or ads complete playing and pass playhead position<br />
|-<br />
| 'sendID3' || Used to send the ID3 tag payload retrieved from the stream || Needs to be called at the beginning of playback<br />
||<br />
|-<br />
| 'end' || Content end || Call when the current video asset completes playback or when a stream is interrupted. <br/><br />
Example: At the end of the content stream, if the user switches to another piece of content, when the browser is refreshed or closed.<br />
|}<br />
<br />
=== Configure API calls - play ===<br />
{{ExampleCode|<br />
|Objective C = <syntaxhighlight lang="objective-c"> [nielsenAppApi play:(loadChannelInfo)];</syntaxhighlight><br />
|Swift = <syntaxhighlight lang="swift">nielsenAppApi?.play(loadChannelInfo);</syntaxhighlight><br />
}}<br />
<br />
=== Configure API calls - loadMetadata ===<br />
Use <code>loadMetadata</code> to pass ‘content’ and ‘ad’ <code>Digital Measurement Metadata</code>. The CMS data must be passed as a JSON object.<br />
{{ExampleCode|<br />
|Objective C = <syntaxhighlight lang="objective-c">[nielsenApi loadMetadata:(loadDtvr)];</syntaxhighlight><br />
|Swift = <syntaxhighlight lang="swift">self.nielsenAppApi?.loadMetadata(loadDtvr)</syntaxhighlight><br />
}}<br />
<br />
=== Configure API calls - sendID3 ===<br />
[[sendID3]] API is a receiver for timed metadata events (ID3 tags) provided through iOS’s NSNotificationCenter notification system. This API filters out Nielsen-specific ID3 tags from the system and buffers the data for transfer to Nielsen’s collection facility.<br />
{{ExampleCode|<br />
|Objective C = <syntaxhighlight lang="objective-c">[nielsenApi sendID3:extraString];</syntaxhighlight><br />
|Swift = <syntaxhighlight lang="swift"> [nielsenApi sendID3:extraString];</syntaxhighlight>}}<br />
'''Sample ID3 tags''' <br />
* <code>www.nielsen.com/X100zdCIGeIlgZnkYj6UvQ==/X100zdCIGeIlgZnkYj6UvQ==/AAAB2Jz2_k74GXSzx4npHuI_<wbr />JwJd3QSUpW30rDkGTcbHEzIMWleCzM-uvNOP9fzJcQMWQLJqzXMCAxParOb5sGijSV9dNM3QiBniJYGZ5GI-lL1fXTTN0IgZ4iWBmeRiPpS9AAAAAAAAAAAAAAAAAAAAAFJWFM5SVhTONNU=/00000/00000/00</code><br />
* <code>www.nielsen.com/X100zdCIGeIlgZnkYj6UvQ==/R8WHe7pEBeqBhu8jTeXydg==/AAICoyitYqlxT7n6aZ0oMCGhe<wbr />Fi4CXFp46AMUPZz1lMr_M9tr3_cjee1SHqxrOiVerMDLeyn9xzocZSKwi746Re8vNOtpNCAZjYABs_J0R25IHpvOc1HS8<wbr />QHGgD5TgOJeS6gX100zdCIGeIlgZnkYj6UvVJWFNhSVhTiPE0=/00000/46016/00</code><br />
Refer to [[iOS SDK API Reference#Retrieving ID3 Tags|Retrieving ID3 Tags]] section to know more details.<br />
<br />
=== Configure API calls - stop ===<br />
Call <code>stop</code> in case of interruptions during playback like flight mode, Wi-Fi toggle, etc. Call <code>play</code> when resuming the stream / starting the new stream.<br />
{{ExampleCode|<br />
|Objective C = <syntaxhighlight lang="objective-c">[nielsenApi stop];</syntaxhighlight><br />
|Swift = <syntaxhighlight lang="swift">nielsenApi.stop()</syntaxhighlight><br />
}}<br />
<br />
=== Configure API calls - end ===<br />
Call [[end]] only at the end of playback.<br />
{{ExampleCode|<br />
|Objective C = <syntaxhighlight lang="objective-c">[nielsenApi end];</syntaxhighlight><br />
|Swift = <syntaxhighlight lang="swift">nielsenApi.end()</syntaxhighlight><br />
}}<br />
<br />
== Retrieving ID3 Tags ==<br />
ID3 tags have a payload of about 249 characters and start with "www.nielsen.com".<br />
<br />
ID3 tags are extracted by observing a property called timedMetadata on the iOS player item. Now this is done via a concept called KVO (Key Value Observing), where you register interest in a property, and the runtime will let you know when it has changed.<br />
<br />
Both the iOS native players have the ability to extract ID3 tags, If any other player apart from iOS native players (AVPlayer, MPMoviePlayer) is used, check and ensure that the player has the capability to extract ID3 tags.<br />
<br />
=== Examples of extracting ID3 tags from the iOS Native Player ===<br />
{{ExampleCode|<br />
|Objective C = <syntaxhighlight lang="objective-c"><br />
//Adding observer to player to track play,pause and reverse<br />
[player addObserver:self<br />
forKeyPath:@"rate"<br />
options:(NSKeyValueObservingOptionNew)<br />
context:nil];<br />
</syntaxhighlight><br />
------------------------------<br />
<syntaxhighlight lang="objective-c"> <br />
//Setting observer to track timedMetadata<br />
[player addObserver:self<br />
forKeyPath: timedMetadataKey<br />
options: (NSKeyValueObservingOptionNew)<br />
context: &TimedMetadataObserverContext];<br />
</syntaxhighlight><br />
------------------------------<br />
<syntaxhighlight lang="objective-c"> <br />
- (void)observeValueForKeyPath:(NSString *)keyPath<br />
ofObject:(id)object<br />
change:(NSDictionary *)change<br />
context:(void *)context<br />
{<br />
if(keyPath == timedMetadataKey){<br />
if(context == &TimedMetadataObserverContext){<br />
<br />
id newMetadataArray = [change objectForKey:NSKeyValueChangeNewKey];<br />
if (newMetadataArray != [NSNull null])<br />
{<br />
array = newMetadataArray;<br />
for (AVMetadataItem *metadataItem in array)<br />
{<br />
//Handling TimedMetadata<br />
[self handleTimedMetadata: metadataItem];<br />
}<br />
}<br />
<br />
}<br />
}<br />
</syntaxhighlight><br />
------------------------------<br />
<syntaxhighlight lang="objective-c"> <br />
- (void)handleTimedMetadata:(AVMetadataItem *)timedMetadata<br />
{<br />
// We expect the content to contain plists encoded as timed metadata<br />
// AVPlayer turns these into NSDictionaries<br />
<br />
id extraAttributeType = [timedMetadata extraAttributes];<br />
NSString *extraString = nil;<br />
if ([extraAttributeType isKindOfClass:[NSDictionary class]])<br />
{<br />
extraString = [extraAttributeType valueForKey:@"info"];<br />
}<br />
else if ([extraAttributeType isKindOfClass:[NSString class]])<br />
{<br />
extraString = extraAttributeType;<br />
}<br />
<br />
NSString *key = [NSString stringWithFormat:@"%@", [timedMetadata key]];<br />
<br />
//If tag starts with "www.nielsen.com", then only sending to SDK<br />
if ([key isEqualToString:@"PRIV"] && [extraString rangeOfString:@"www.nielsen.com"].length > 0)<br />
{<br />
<br />
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{<br />
[nielsenApi sendID3:extraString];<br />
});<br />
}<br />
}<br />
</syntaxhighlight><br />
|Swift = <syntaxhighlight lang="swift"> <br />
//Setting observer to track timedMetadata<br />
player.addObserver(self, forKeyPath: timedMetadataKey, options: NSKeyValueObservingOptions.new, context: &TimedMetadataObserverContext)</syntaxhighlight><br />
------------------------------------------------------<br />
<syntaxhighlight lang="swift"> <br />
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {<br />
<br />
if keyPath == timedMetadataKey {<br />
if(context == &TimedMetadataObserverContext){<br />
if change != nil {<br />
let timedMetadataArray = change![.newKey]<br />
if timedMetadataArray != nil && (timedMetadataArray! as AnyObject) is Array<Any> {<br />
for item in timedMetadataArray as! [AVMetadataItem] {<br />
//Handling TimedMetadata<br />
self.handleTimedMetadata(metadataItem: item)<br />
}<br />
}<br />
}<br />
}<br />
}<br />
</syntaxhighlight><br />
------------------------------------------------------<br />
<syntaxhighlight lang="swift"> <br />
func handleTimedMetadata(metadataItem: AVMetadataItem) {<br />
guard let extraAttributeType = metadataItem.extraAttributes else {<br />
return<br />
}<br />
let info : AVMetadataExtraAttributeKey = AVMetadataExtraAttributeKey(rawValue: "info")<br />
let extraString = extraAttributeType[info] as AnyObject<br />
let key = metadataItem.key as! String<br />
<br />
//If tag starts with "www.nielsen.com", then only sending to SDK<br />
if key == "PRIV" && extraString.range(of: "www.nielsen.com").length > 0 {<br />
<br />
DispatchQueue.global(qos: .default).async { () -> Void in<br />
self.nielsenApi?.sendID3(extraString as! String)<br />
}<br />
}<br />
}<br />
<br />
</syntaxhighlight><br />
}}<br />
<br />
== Life cycle of SDK instance ==<br />
Life cycle of SDK instance includes four general states:<br />
# '''Initial state''' – The SDK is not initialized and hence, not ready to process playing information. Once the SDK is moved out of this state, it needs instantiation of the new SDK instance in order to get the instance in the '''Initial state'''.<br />
# '''Idle state''' – The SDK is initialized and is ready to process playing information. Once Initialized, the SDK instance is not processing any data, but is listening for the play event to occur.<br />
# '''Processing state''' – The SDK instance is processing playing information. The <code>'''play'''</code> and <code>'''loadMetadata''' </code> calls move the SDK instance into this state. In this state, the SDK instance will be able to process the following calls.<br />
## <code>'''stop'''</code> – Call this API when the playback is paused, switches between content and ad (within the same content playback) or encounters interruptions.<br />
## <code>'''end'''</code> – SDK instance exits from Processing state when this API is called.<br />
# '''Disabled state''' – The SDK instance is disabled and is not processing playing information. SDK instance moves into this state in one of the following scenarios.<br />
## Initialization fails<br />
## <code>'''appDisableApi'''</code> is set to <code>true</code> ''(This is testing purposes only. Not for User Opt-Out.)''<br />
<br />
<blockquote>'''Note:''' For API Version 5.1 and above, App SDK will fire data pings and continue measurement even after the user has opted out from Nielsen measurement on a device. The data ping will be marked as opted-out ping.</blockquote><br />
<br />
'''Note''': In case of any interruptions during playback due to alarm, calendar, call, flight mode, Wi-Fi toggle, channel change, etc., call <code>stop</code> to stop the measurement.<br />
<br />
== Handling Foreground and Background states ==<br />
For iOS, background/foreground detection is handled by the app lifecylce APIs which are provided by [https://developer.apple.com/library/content/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/TheAppLifeCycle/TheAppLifeCycle.html Apple:]<br />
<br />
Foreground/Background state measurement is a requirement of Nielsen AppSDK implementation which is especially crucial for static measurement.<br />
<br />
== Interruptions during playback ==<br />
As part of integrating Nielsen App SDK with the player application, the Audio / Video app developer needs to handle the following possible interruption scenarios:<br />
* Pause / Play<br />
* Network Loss (Wi-Fi / Airplane / Cellular)<br />
* Call Interrupt (SIM or Third party Skype / Hangout call)<br />
* Alarm Interrupt<br />
* Content Buffering<br />
* Device Lock / Unlock (Video players only, not for Audio players)<br />
* App going in the Background/Foreground (Video players only, not for Audio players)<br />
* Channel / Station Change Scenario<br />
* Unplugging of headphone<br />
In case of encountering one of the above interruptions, the player application needs to<br />
* Call <code>stop</code> immediately (except when content is buffering) and withhold sending playhead position.<br />
* Call <code>play</code> once the playback resumes.<br />
<br />
== Pre-Certification Checklists ==<br />
After the application is ready to be sent for Nielsen Certification, please go through the [[Digital Pre-Certification Checklist App SDK]] and ensure the app behaves as expected, before submitting to Nielsen.<br />
<br />
{{Template:iOS_Privacy_and_Opt-Out}}<br />
<br />
== Going Live ==<br />
Following Nielsen testing, users need to make one update to the initialization call to ensure that the site is being measured properly.<br />
<br />
# '''Debug Logging''': Disable logging by deleting <code>{nol_sdkDebug: 'DEBUG'}</code> from initialization call.<br />
<br />
'''Note''': before going live you have to inform Nielsen team - this is necessary, because Nielsen team has to adjust internal configuration parameter to enable data collection. Without that notification no data will be collected and no data will be reported.<br />
<br />
== Sample Applications ==<br />
The below sample applications have been designed to show the API's functionality and are broken into two distinct categories:<br />
* '''Basic''' - To show the functionality of the Nielsen API using a standard no-frills player.<br />
** [[Swift Basic Sample|Swift 4.0 Sample]]<br />
** [[Objective-c Basic example|Objective-C Sample]]<br />
<br />
* '''Advanced''' - Nielsen Simplified API integrated into a custom video player is contained in the ZIP package.<br />
<br />
== Testing an Implementation - App ==<br />
See [[Digital Measurement Testing]].</div>AlexGutierrezhttps://engineeringportal.nielsen.com//w/index.php?title=DCR_Video_iOS_SDK&diff=3615DCR Video iOS SDK2019-04-29T15:44:29Z<p>AlexGutierrez: </p>
<hr />
<div>{{Breadcrumb|}} {{Breadcrumb|Digital}} {{Breadcrumb|DCR & DTVR}} {{CurrentBreadcrumb}}<br />
[[Category:Digital]]<br />
<br />
== Overview ==<br />
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.<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), and [[Digital Audio]]. Nielsen SDKs are also equipped to measure static content and can track key life cycle events of an application like:<br />
*Application launch events and how long app was running<br />
*Time of viewing a sub section / page in the application.<br />
<br />
== Prerequisites ==<br />
To start using the App SDK, the following items are required:<br />
{| class="wikitable"<br />
|-<br />
! style="width: 30px;" |<br />
! style="width: 15%;" | Item<br />
! Description<br />
! Source<br />
|-<br />
|| ☑ || '''App ID (appid)''' || Unique ID assigned to the player/site and configured by product. || Contact Nielsen<br />
|-<br />
|| ☑ || '''sfcode''' || Environment that the SDK must point to || Contact Nielsen<br />
|-<br />
|| ☑ || '''Nielsen SDK''' || Includes SDK libraries and '''sample implementation'''; ''See [[iOS SDK Release Notes]]'' || [[Special:Downloads|Download]]<br />
|}<br />
<br />
If you do not have any of these pre-requisites or if you have any questions, please contact our SDK sales support team.<br />
Refer to [[Digital Measurement Onboarding]] guide for information on how to get a Nielsen App SDK and appid.<br />
___TOC___<br />
== Implementation ==<br />
This guide covers implementation steps for iOS using Xcode utilizing the Standard Nielsen SDK for DCR.<br />
<br />
== Setting up your Development Environment ==<br />
Prior to SDK Version 6.2.0.0 the IOS framework has been distributed as a static library packaged into framework bundle format. Apple recommends to use dynamic framework, it has some benefits over static libraries like less executable file size of an app, faster startup time and native support in xCode IDE. Nielsen AppSDK has been transformed into dynamic framework in this release ([[iOS_Static_Framework_Setup|static framework]] is still available).<br />
<br />
If migrating from the static library to this new dynamic framework, once implemented, unless your specific application requires, you can remove the following Frameworks that were once required:<code> [AdSupport, JavascriptCore, SystemConfiguration, Security, AVFoundation, libc++] </code><br />
<br><br />
<br />
The Dynamic framework is created as a fat framework. It means that it contains slices required for devices (armv7, arm64) as well as slices required for simulators (i386, x86_64). Simulator slices are needed to let clients build and debug their app on the simulators, but they should be removed before sending the app to the AppStore. The example of the shell script that should be added as a Run Script phase in the application can be [[DCR_Video_iOS_SDK#Removing_Simulators|found below]].<br />
<br />
=== How to obtain the NielsenAppApi.Framework ===<br />
The Nielsen AppSDK can either be downloaded directly or can be integrated directly within an application through the use of CocoaPods. We recommend using the CocoaPods-based integration whenever possible to ensure you maintain the most recent changes and enhancements to the Nielsen libraries.<br />
* [[Digital_Measurement_iOS_Artifactory_Guide|Select to obtain CocoaPods implementation guide]]<br />
* [[Special:Downloads|Select to Download Directly]]<br />
<br />
=== Configuring Xcode Development Environment ===<br />
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<br />
<br />
<blockquote>'''Note''': All communications between the SDK and the Census (Collection Facility) use HTTPS.</blockquote><br />
<br />
=== Download Framework ===<br />
The first step is to download and copy the [[Special:Downloads|NielsenAppApi.framework]] bundle to the app project directory.<br />
=== Add Framework ===<br />
In the General tab for app configuration add NielsenAppApi.framework in the list of Embedded Binaries.<br />
=== Add Path ===<br />
Add path to the NielsenAppApi.framework in the Framework Search Paths build setting.<br />
=== Import Framework ===<br />
Add NielsenAppApi.framework module in the source file of your app:<br />
<br />
==== Using Swift ====<br />
To import a set of Objective-C files in the same app target as your Swift code, you rely on an Objective-C bridging header to expose those files to Swift. Xcode offers to create this header file when you add a Swift file to an existing Objective-C app, or an Objective-C file to an existing Swift app.<br />
*Select File/New File/Objective-C File<br />
*Xcode will prompt you to create a bridging header.<br />
[[File:bridgingheader 2x.png|600px|center|link=]]<br />
Once this file has been created, you need to add the following:<br />
<syntaxhighlight lang="swift"><br />
#import <NielsenAppApi/NielsenAppApi.h><br />
</syntaxhighlight><br />
<br />
==== Using Objective-C ====<br />
Add the code to the View Controller’s header file.<br />
<syntaxhighlight lang ="objective-c"><br />
#import <NielsenAppApi/NielsenAppApi.h><br />
</syntaxhighlight><br />
<br />
== SDK Initialization ==<br />
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)<br />
<br />
* A maximum of four SDK instances per appid are supported. [[Dual_Instances_of_SDK|(Click here for an example of multiple instances)]]<br />
* When four SDK instances exist, you must destroy an old instance before creating a new one.<br />
<br />
The following table contains the list of arguments that can be passed via the AppInfo JSON schema.<br />
<br />
* The appid is provided by the Nielsen Technical Account Manager (TAM). The appid is a GUID data type and is specific to the application.<br />
{| class="wikitable"<br />
|-<br />
! Parameter / Argument !! Description !! Source !! Required? !! Example<br />
|-<br />
| appid || Unique id for the application assigned by Nielsen. It is GUID data type.|| Nielsen-specified || Yes || PXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX<br />
|-<br />
| appname || Name of the application || Client-defined || Optional; automatically detected in SDK 6.0.0.4 and above || Nielsen Sample App<br />
|-<br />
| sfcode || Nielsen collection facility to which the SDK should connect.<br />
'''DTVR'''<br />
* "us"<br />
'''Digital Audio'''<br />
* "drm"<br />
'''DCR'''<br />
* "dcr" <br />
|| Nielsen-specified || Yes || dcr<br />
|-<br />
|containerID || View ID of the UI element used as player view in application for Viewability ||Client-defined||Optional||"1234567"<br />
|-<br />
| nol_devDebug || Enables Nielsen console logging. Only required for testing<br />
|| Nielsen-specified || Optional || "DEBUG"<br />
|}<br />
<br />
== Debug flag for development environment ==<br />
Player application developers / integrators can use Debug flag to check whether an App SDK API call made is successful. To activate the Debug flag,<br />
Pass the argument <code>@"nol_devDebug":@"INFO"</code>, in the JSON string . The permitted values are:<br />
<br />
* '''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.<br />
* '''WARN''': Indicates potential integration / configuration errors or SDK issues.<br />
* '''ERROR''': Indicates important integration errors or non-recoverable SDK issues.<br />
* '''DEBUG''': Debug logs, used by the developers to debug more complex issues.<br />
<br />
Once the flag is active, it logs each API call made and the data passed. The log created by this flag is minimal.<br />
<blockquote>'''Note''': DO NOT activate the Debug flag in a production environment.</blockquote><br />
<br />
=== Sample SDK Initialization Code ===<br />
{{ExampleCode|<br />
|Swift = <br />
Swift 4.0 Example:<br />
<code>NielsenInit.swift</code><br />
<syntaxhighlight lang="swift"><br />
import Foundation<br />
import NielsenAppApi<br />
<br />
class NielsenInit : NSObject {<br />
class func createNielsenApi(delegate: NielsenAppApiDelegate) -> NielsenAppApi?{<br />
<br />
let appInformation:[String: String] = [<br />
<br />
"appid": "PDA7D5EE6-B1B8-4123-9277-2A788XXXXXXX",<br />
"sfcode": "dcr",<br />
"nol_devDebug": "DEBUG"<br />
"containerId": String(containerId) //Keep container id unique constant, you can use tag property of player.<br />
]<br />
<br />
return NielsenAppApi(appInfo:appInformation, delegate:delegate)<br />
}<br />
}<br />
</syntaxhighlight><br />
<br />
Sample code using AVPlayer.<br />
<code>ViewController.swift</code><br />
<br />
<syntaxhighlight lang="swift"><br />
class ViewController: UIViewController, NielsenAppApiDelegate, AVPlayerViewControllerDelegate {<br />
<br />
// your code// <br />
<br />
override func viewDidLoad() {<br />
super.viewDidLoad()<br />
<br />
//Getting the instance of NielsenApi<br />
self.nielsenApi = NielsenInit.createNielsenApi(delegate: self)<br />
<br />
}<br />
}<br />
</syntaxhighlight><br />
|Objective C = <br />
Initialize the Nielsen App object within the viewDidLoad view controller delegate method using initWithAppInfo:delegate:<br />
<blockquote>If App SDK is initialized using init or new methods, it will ignore the API calls resulting in no measurement. The SDK will not return any errors.</blockquote><br />
<syntaxhighlight lang="objective-c"> <br />
#import "NielsenInit.h"<br />
#import <NielsenAppApi/NielsenEventTracker.h><br />
<br />
@implementation NielsenInit<br />
<br />
+ (NielsenEventTracker *)createNielsenEventTrackerWithDelegate:(id<NielsenEventTrackerDelegate>)delegate<br />
{<br />
//Initialising the NielsenEventTracker class by passing app information which returns the instance of NielsenEventTracker.<br />
<br />
NSDictionary *appInformation = @{ @"appid": @"PDA7D5EE6-B1B8-4123-9277-2A788XXXXXXX",<br />
@"appversion": @"1.0",<br />
@"sfcode": @"dcr",<br />
@"nol_devDebug": @"DEBUG",<br />
@"containerId": @"1" };<br />
<br />
return [[NielsenEventTracker alloc] initWithAppInfo:appInformation delegate:delegate];<br />
}<br />
<br />
@end<br />
</syntaxhighlight><br />
<br />
<br />
The following would be the <code>NielsenInit.h</code> file:<br />
<syntaxhighlight lang="objective-c"><br />
<br />
#import <Foundation/Foundation.h><br />
<br />
@class NielsenEventTracker;<br />
@protocol NielsenEventTrackerDelegate;<br />
<br />
@interface NielsenInit : NSObject<br />
<br />
+ (NielsenEventTracker *)createNielsenEventTrackerWithDelegate:(id<NielsenEventTrackerDelegate>)delegate;<br />
<br />
@end<br />
</syntaxhighlight><br />
<br />
}}<br />
<br />
<!--<br />
== Initializing the Nielsen AppSDK to measure the Viewability ==<br />
The integrator to support the viewability metrics in the application has to provide a tag value of the player view to let Nielsen AppSDK know that there is a player that needs to be tracked. It’s called the ‘containerId’ and it should be passed in application info dictionary as string while initializing the Nielsen AppSDK.<br />
<br />
=== Android ===<br />
{| class="wikitable"<br />
|-<br />
! # !! Parameter Name !! Description !! Supported Values !! Example<br />
|-<br />
| 1 || containerId ||View ID of the UI element used as player view in application. getId() method of View class can be used to get this value. ||A positive integer used to identify the view. || 2131558561<br />
|}<br />
<br />
=== iOS ===<br />
{| class="wikitable"<br />
|-<br />
! # !! Parameter Name !! Description !! Supported Values !! Example<br />
|-<br />
| 1 || containerId ||The tag of the UIView that represents the Player View ||The string value representing the NSInteger value with maximum value of NSIntegerMax that is related on 32- or 64-bit applications. || "100" <br> "2131558561"<br />
|}<br />
<br />
For iOS it is required to link additional frameworks that are needed for viewability engine:<br><br />
<code>JavaScriptCore.framework</code> <br><br />
<code>WebKit.framework</code><br />
<br />
The Nielsen AppSDK uses a tracking WebView (TWV) approach. For more information on Viewability, please refer to [https://engineeringportal.nielsen.com/docs/Implementing_Viewability_with_AppSDK Implementing Viewability with AppSDK.]<br />
--><br />
<br />
== APP SDK Error & Event Codes ==<br />
To view the Error and Event codes for iOS and Android, please review the [[APP SDK Event Codes|App SDK Event Code]] Reference page.<br />
<br />
== Configure Payload ==<br />
=== Handling JSON Metadata ===<br />
All the SDK methods handles only two types of objects: NSString, NSDictionary. The parameters passed must be either a JSON formatted string or a NSDictionary object. The JSON passed in the SDK must be well-formed.<br />
* NSDictionary object<br />
** If an object of unexpected type is passed to the method, the error message will be logged.<br />
** If string has invalid JSON format, the error message will be logged.<br />
* JSON value must be string value.<br />
** This includes boolean and numeric values. For example, a value of true should be represented with "true", number value 123 should be "123".<br />
** All the Variable Names like appid, appname, sfcode, dataSrc, title, type etc. are case-sensitive. Use the correct variable name as specified in the documentation.<br />
* JSON string can be prepared using either raw NSString or serialized NSDictionary.<br />
{{ExampleCode|<br />
|Swift = <br />
<syntaxhighlight lang="swift"><br />
<br />
let channelInfo = [<br />
"channelName": "My Channel Name 1",<br />
];<br />
<br />
let contentMetadata = [<br />
"type": "content",<br />
"assetid": "88675545",<br />
"title": "Program S3, EP1",<br />
"isfullepisode":"No",<br />
"program":"Program Name",<br />
"length":"3600",<br />
"airdate":"20171020 10:05:00",<br />
"segB":"CustomSegmentValueB", //optional<br />
"segC":"CustomSegmentValueC", //optional<br />
];<br />
</syntaxhighlight> <br />
|Objective C = <syntaxhighlight lang="objective-c"> <br />
NSDictionary *channelInfo = @<br />
{<br />
@"channelname":@"My Channel Name 1",<br />
}<br />
<br />
NSDictionary *contentMetadata = @<br />
{@"type": @"content",<br />
@"assetid":@"C77664",<br />
@"title":@"S2,E3",<br />
@"isfullepisode":@"y",<br />
@"program":@"MyProgram",<br />
@"length": @"3600",<br />
@"airdate":@"20180120 10:00:00",<br />
@"adloadtype":@"2",<br />
@"segB": @"CustomSegmentValueB", //optional<br />
@"segC": @"CustomSegmentValueC", //optional<br />
}<br />
</syntaxhighlight><br />
}}<br />
<br />
=== Configure metadata === <br />
channelName should remain constant throughout the completion of an episode or live stream.<br />
{| class="wikitable"<br />
|-<br />
! Key !! Description !! Values !! Required<br />
|-<br />
| channelName || ChannelInfo refers to the Channel name. This can be a free-form value<br />
value such as a friendly name for the content being played. the SDK<br/><br />
will pass the application name automatically.<br />
|| custom || No<br />
|-<br />
|}<br />
<br />
=== Content metadata ===<br />
Content metadata should remain constant throughout the entirety of an episode/clip including when ads play.<br />
{{DCR Content Metadata}}<br />
<br />
=== Ad Metadata ===<br />
The ad metadata (if applicable) should be passed for each individual ad, if ads are available during or before the stream begins.<br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| type || ad vs content || <code>'ad'</code>|| ✓<br />
|-<br />
| assetid || unique ID assigned to ad || custom || ✓<br />
|}<br />
<br />
=== Example Ad Object ===<br />
<syntaxhighlight lang="javascript"><br />
// create ad object<br />
"ad": {<br />
"type": "preroll",<br />
"assetid": "AD-ID123"<br />
}<br />
</syntaxhighlight><br />
<br />
== Configure API Calls ==<br />
[[File:appsdkTimeline-DCR.png|icon|link=]]<br />
=== Sample API Sequence ===<br />
A Sample API sequence could follow this flow:<br />
{| class="wikitable"<br />
|-<br />
! Type !! Sample code !! Description<br />
|-<br />
|On App Start||<code>[nielsenMeter loadMetadata: contentMetadata];</code> || // contentMetadata Object contains the JSON metadata for the impression<br />
|-<br />
| rowspan="2" | Start of stream || <code>[nielsenMeter play: channelName];</code> || // channelName now automatically generated by Nielsen SDK<br />
|-<br />
| <code>[nielsenMeter loadMetadata: contentMetadataObject];</code> || // contentMetadataObject contains the JSON metadata for the content being played<br />
|-<br />
| Content || <code>[nielsenMeter playheadPosition: position];</code> || // playheadPosition is position of the playhead while the content is being played<br />
|-<br />
| End of Stream || <code>[nielsenMeter end];</code> || // Content playback is completed.<br />
|}<br />
<br />
=== SDK Events ===<br />
{| class="wikitable"<br />
|-<br />
! Event !! Parameter !! Description<br />
|-<br />
| 'loadMetadata' || content/ad metadata object || Needs to be called at the beginning of each asset<br />
|-<br />
| 'playheadPosition' || playhead position as integer<br/><br />
VOD: current position in seconds <br/><br />
Live: current UNIX timestamp (seconds since Jan-1-1970 UTC) <br/><br />
Note: 'PlayheadPosition' has to be called every second<br />
||<br />
Pass playhead position every second during playback<br />
|-<br />
| 'stop' || playhead position || Call when content or ads complete playing and pass playhead position<br />
|-<br />
| 'end' || playhead position in seconds || Call when the current video asset completes playback and pass the playhead position. <br/><br />
Example: At the end of the content stream, if the user switches to another piece of content, when the browser is refreshed or closed.<br />
|}<br />
<blockquote>Note: For livestream, send the UNIX timestamp, for VOD send the time in seconds as integer. The final playhead position must be sent for the current asset being played before calling <code>'''stop'''</code>, <code>'''end'''</code> or<code> '''loadmetadata'''</code>,.</blockquote><br />
<br />
=== Life cycle of SDK instance ===<br />
Life cycle of SDK instance includes four general states:<br />
# '''Initial state''' – The SDK is not initialized and hence, not ready to process playing information. Once the SDK is moved out of this state, it needs instantiation of the new SDK instance in order to get the instance in the '''Initial state'''.<br />
# '''Idle state''' – The SDK is initialized and is ready to process playing information. Once Initialized, the SDK instance is not processing any data, but is listening for the play event to occur.<br />
# '''Processing state''' – The SDK instance is processing playing information. The <code>'''play'''</code> and <code>'''loadMetadata''' </code> calls move the SDK instance into this state. In this state, the SDK instance will be able to process the following calls.<br />
## <code>'''playheadPosition'''</code> – Call this API every one second when playhead position timer is fired. If a LIVE event, use the current UNIX timestamp (seconds since Jan-1-1970 UTC).<br />
## <code>'''stop'''</code> – Call this API when the playback is paused, switches between content and ad (within the same content playback) or encounters interruptions.<br />
## <code>'''end'''</code> – SDK instance exits from Processing state when this API is called.<br />
# '''Disabled state''' – The SDK instance is disabled and is not processing playing information. SDK instance moves into this state in one of the following scenarios.<br />
## Initialization fails<br />
## <code>'''appDisableApi'''</code> is set to <code>true</code><br />
<br />
<blockquote>'''Note:''' For API Version 5.1 and above, App SDK will fire data pings and continue measurement even after the user has opted out from Nielsen measurement on a device. The data ping will be marked as opted-out ping.<br />
<br />
'''Note''': In case of any interruptions during playback due to alarm, calendar, call, flight mode, Wi-Fi toggle, channel change, etc., call [[stop]] to stop the measurement.<br />
* As soon as the playback resumes, call <code>'''play'''</code>, <code>'''loadMetadata''' </code> and <code>'''playheadPosition'''</code> </blockquote><br />
<br />
=== API Call Sequence ===<br />
==== Use Case 1: Content has no Advertisements ====<br />
Call [[play()]] with channelName JSON as below.<br />
<syntaxhighlight lang="json">{<br />
"channelName": "TheMovieTitle"<br />
}</syntaxhighlight><br />
Call [[loadMetadata()]] with JSON metadata for content as below.<br />
<syntaxhighlight lang="json">{<br />
"type": "content",<br />
"assetid": "vid345-67483",<br />
"program": "ProgramName",<br />
"title": "Program S3, EP1",<br />
"length": "3600",<br />
...<br />
}</syntaxhighlight><br />
Call [[setPlayheadPosition()]] every one second until a pause / stop.<br />
Use the sample API sequence below as a reference to identify the specific events that need to be called during content playback without ads.<br />
{| class="wikitable"<br />
|-<br />
! Type !! Sample code !! Description<br />
|-<br />
| rowspan="2" | Start of stream || <code>mAppSdk.play(channelName); </code> || // channelName contains JSON metadata of channel/video name being played<br />
|-<br />
| <code>mAppSdk.loadMetadata(contentMetaDataObject);</code> || // contentMetadataObject contains the JSON metadata for the content being played<br />
|-<br />
| Content || <code>mAppSdk.setPlayheadPosition(playheadPosition);</code> || // position is position of the playhead while the content is being played<br />
|-<br />
| End of Stream || <code>mAppSdk.end();</code> || // Content playback is completed.<br />
|}<br />
<br />
==== Use Case 2: Content has Advertisements ====<br />
Call [[play()]] with channelName JSON as below.<br />
<syntaxhighlight lang="json">{<br />
"channelName": "TheMovieTitle"<br />
}</syntaxhighlight><br />
Call [[loadMetadata()]] with JSON metadata for ad as below.<br />
<syntaxhighlight lang="json">{<br />
"type": "preroll",<br />
"assetid": "ad=123"<br />
}</syntaxhighlight><br />
<blockquote>Note: In case the individual ad details are not available, send ad pod (presence) details through the [[loadMetadata]] and playhead position through [[playheadPosition]].</blockquote><br />
<br />
Call [[setPlayheadPosition()]] every one second until a pause / stop / another [[loadMetadata()]] is called. Playhead should be passed for the entire duration of ad pod, if the ad pod details are passed as part of [[loadMetadata()]].<br />
<br />
The sample API sequence can be used as a reference to identify the specific events that need to be called during content and ad playback.<br />
{| class="wikitable"<br />
|-<br />
! Type !! Sample code !! Description<br />
|-<br />
| rowspan="2" | Start of stream || <code>mAppSdk.play(channelName); </code> || // channelName contains JSON metadata of channel/video name being played<br />
|-<br />
| <code>mAppSdk.loadMetadata(contentMetaDataObject);</code> || // contentMetadataObject contains the JSON metadata for the content being played<br />
|-<br />
| rowspan="3" | Preroll || <code>mAppSdk.loadMetadata(prerollMetadataObject);</code> || // prerollMetadataObject contains the JSON metadata for the preroll ad<br />
|-<br />
| <code>mAppSdk.setPlayheadPosition(playheadPosition);</code> || // position is position of the playhead while the preroll ad is being played<br />
|-<br />
| <code>mAppSdk.stop();</code> || // Call stop after preroll occurs<br />
|-<br />
| rowspan="3" | Content || <code>mAppSdk.loadMetadata(contentMetaDataObject);</code> || // contentMetadataObject contains the JSON metadata for the content being played<br />
|-<br />
| <code>mAppSdk.setPlayheadPosition(playheadPosition);</code> || // position is position of the playhead while the content is being played<br />
|-<br />
| <code>mAppSdk.stop();</code> || // Call stop after the content is paused (ad starts)<br />
|-<br />
| rowspan="6" | Midroll || <code>mAppSdk.loadMetadata(midrollMetaDataObject);</code> || // midrollMetadataObject contains the JSON metadata for the midroll ad<br />
|-<br />
| <code>mAppSdk.setPlayheadPosition(playheadPosition);</code> || // position is position of the playhead while the midroll ad is being played<br />
|-<br />
| <code>mAppSdk.stop();</code> || // App moves to background(midroll pauses) <br />
|-<br />
| <code>mAppSdk.loadMetadata(midrollMetaDataObject);</code> || // App moves to foreground (midroll resumes) <br />
|-<br />
| <code>mAppSdk.setPlayheadPosition(playheadPosition);</code> || // playheadPosition is position of the playhead while the midroll ad is being played <br />
|-<br />
| <code>mAppSdk.stop();</code> || // Call stop after midroll occurs<br />
|-<br />
| rowspan="3" | Content (End of stream) || <code>mAppSdk.loadMetadata(contentMetaDataObject);</code> || // contentMetadataObject contains the JSON metadata for the content being played<br />
|-<br />
| <code>mAppSdk.setPlayheadPosition(playheadPosition);</code> || // position is position of the playhead while the content is being played<br />
|-<br />
| <code>mAppSdk.stop();</code> || // Always call stop irrespective of postroll is followed or not<br />
|-<br />
| End of Stream || <code>mAppSdk.end();</code> || // Call end() at the end of content<br />
|-<br />
| rowspan="3" | Postroll || <code>mAppSdk.loadMetadata(postrollMetaDataObject);</code> || // postrollMetadataObject contains the JSON metadata for the postroll ad<br />
|-<br />
| <code>mAppSdk.setPlayheadPosition(playheadPosition);</code> || // position is position of the playhead while the postroll ad is being played<br />
|-<br />
| <code>mAppSdk.stop();</code> || // Call stop after postroll occurs<br />
|}<br />
<br />
<blockquote>Note: Each Ad playhead should reset or begin from 0 at ad start. When content has resumed following an ad break, playhead position must continue from where previous content segment was left off.</blockquote><br />
<br />
== Sequence of Calls ==<br />
=== play ===<br />
Use [[DCR_Video_APP_SDK#play|play]] to pass the channel descriptor information through channelName parameter when the user taps the '''Play''' button on the player.<br />
{{ExampleCode|<br />
|Objective C = <syntaxhighlight lang="objective-c"> [nielsenAppApi play:(JSONObject channelInfo)];</syntaxhighlight><br />
|Swift = <syntaxhighlight lang="swift">nielsenAppApi?.play(JSONObject channelInfo);</syntaxhighlight><br />
}}<br />
<br />
=== loadMetadata ===<br />
{{ExampleCode|<br />
|Objective C = <syntaxhighlight lang="objective-c">[nielsenApi loadMetadata:(data)];</syntaxhighlight><br />
|Swift = <syntaxhighlight lang="swift">self.nielsenAppApi?.loadMetadata(contentMetadata)</syntaxhighlight><br />
}}<br />
<br />
=== playheadPosition ===<br />
{{ExampleCode|<br />
|Objective C = <syntaxhighlight lang="objective-c"><br />
– (void) playheadPosition: (long long) playheadPos<br />
</syntaxhighlight><br />
<br />
==== Sending playheadposition for content ====<br />
<syntaxhighlight lang="objective-c"><br />
-(void) setPlayHeadPosition {<br />
<br />
//Setting play head position<br />
CMTime timeInterval = CMTimeMakeWithSeconds(1, 1);<br />
[player addPeriodicTimeObserverForInterval:(timeInterval) queue:dispatch_get_main_queue() usingBlock:^(CMTime time){<br />
NSTimeInterval seconds = CMTimeGetSeconds(time);<br />
NSInteger intSec = seconds;<br />
<br />
//Sending data dictionary to SDK with updated playHead position.<br />
[nielsenApi playheadPosition:(intSec)];<br />
}];<br />
}<br />
</syntaxhighlight><br />
<br />
|Swift = <syntaxhighlight lang="swift"><br />
//Monitor the Playhead position of the AVPlayer<br />
let timeInterval: CMTime = CMTimeMakeWithSeconds(1.0,10)<br />
self.avPlayerViewController.player?.addPeriodicTimeObserver(forInterval: timeInterval, queue: DispatchQueue.main) {(elapsedTime: CMTime) -> Void in<br />
if self.avPlayerViewController.player!.currentItem?.status == .readyToPlay {<br />
let time : Float64 = self.avPlayerViewController.player!.currentTime().seconds;<br />
let pos = Int64(time);<br />
NSLog("New Elapse Time = \(time)");<br />
self.nielsenAppApi?.playheadPosition(pos);<br />
}<br />
}<br />
}<br />
</syntaxhighlight><br />
}}<br />
<br />
=== stop ===<br />
{{ExampleCode|<br />
|Objective C = <syntaxhighlight lang="objective-c">[nielsenApi stop];</syntaxhighlight><br />
|Swift = <syntaxhighlight lang="swift">nielsenApi.stop()</syntaxhighlight><br />
}}<br />
<br />
=== end ===<br />
When content stop is initiated and content cannot be resumed from the same position (it can only be restarted from the beginning of stream).<br />
{{ExampleCode|<br />
|Objective C = <syntaxhighlight lang="objective-c">[nielsenApi end];</syntaxhighlight><br />
|Swift = <syntaxhighlight lang="swift">nielsenApi.end()</syntaxhighlight><br />
}}<br />
<br />
== Handling Foreground and Background states ==<br />
For iOS, background/foreground detection is handled by the app lifecylce APIs which are provided by [https://developer.apple.com/library/content/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/TheAppLifeCycle/TheAppLifeCycle.html Apple:]<br />
<br />
Foreground/Background state measurement is a requirement of Nielsen AppSDK implementation which is especially crucial for static measurement.<br />
<br />
== Interruptions during playback ==<br />
As part of integrating Nielsen App SDK with the player application, the Audio / Video app developer needs to handle the following possible interruption scenarios:<br />
* Pause / Play<br />
* Network Loss (Wi-Fi / Airplane / Cellular)<br />
* Call Interrupt (SIM or Third party Skype / Hangout call)<br />
* Alarm Interrupt<br />
* Content Buffering<br />
* Device Lock / Unlock (Video players only, not for Audio players)<br />
* App going in the Background/Foreground (Video players only, not for Audio players)<br />
* Channel / Station Change Scenario<br />
* Unplugging of headphone<br />
In case of encountering one of the above interruptions, the player application needs to<br />
* Call [[stop]] immediately (except when content is buffering) and withhold sending playhead position.<br />
* Start sending pings – <code>'loadMetadata'</code> and <code>'playheadPosition'</code> for the new viewing session, once the playback resumes.<br />
Please see the [https://engineeringportal.nielsen.com/docs/Digital_Measurement_Interruption_Scenarios Interruption Scenarios Page] for more details<br />
<br />
== Pre-Certification Checklists ==<br />
After the application is ready to be sent for Nielsen Certification, please go through the [[Digital Pre-Certification Checklist App SDK]] and ensure the app behaves as expected, before submitting to Nielsen.<br />
<br />
{{Template:iOS_Privacy_and_Opt-Out}}<br />
<br />
== AirPlay ==<br />
To implement OTT measurement, report OTT changes to the SDK using public API interface: [[updateOTT]]<br />
<br />
In order to detect AirPlay and mirroring changes we use AVAudioSessionPortDescription properties that are different on different iOS versions. We found that on iOS versions 8 - 10 <code>AVAudioSessionPortDescription</code> has the following values:<br><br />
<code><br />
AirPlay: type = AirPlay; name = Apple TV 4K; UID = DC:56:E7:53:72:85-airplay <br><br />
Mirroring: type = AirPlay; name = Apple TV 4K; UID = DC:56:E7:53:72:85-screen<br />
</code><br><br />
<br />
For iOS 11+ some parameters like name and UID have different values:<br><br />
<code><br />
AirPlay: type = AirPlay; name = AirPlay; UID = 0eb63aae-5915-45f1-b0f7-0102a0e50d53 <br><br />
Mirroring: type = AirPlay; name = Apple TV 4K; UID = 4335E8A9-1C0A-4251-9000-28CA5FA2F3CF-192731714653291-screen<br><br />
</code><br />
<br />
To implement OTT measurement, report OTT changes to the SDK using public API interface: [[updateOTT]] <br><br />
The following code snipped is suggested for AirPlay / mirroring detection on iOS devices.<br />
<br />
{{ExampleCode|<br />
|Objective C = <br />
<syntaxhighlight lang="objective-c"><br />
– (void)updateOTT:(id)ottInfo;<br />
</syntaxhighlight><br />
=== Subscribe to AVAudioSessionRouteChangeNotification ===<br />
<syntaxhighlight lang="objective-c"><br />
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleRouteChanged:) name:AVAudioSessionRouteChangeNotification object:nil];<br />
</syntaxhighlight><br />
<br />
=== Handle AVAudioSessionRouteChangeNotification and prepare OTT dictionary:===<br />
<syntaxhighlight lang="objective-c"><br />
- (void)handleRouteChanged:(NSNotification *)notification<br />
{<br />
NSMutableDictionary *ottDict = [NSMutableDictionary dictionaryWithDictionary: @{@"ottStatus": @"0"}];<br />
<br />
AVAudioSession *audioSession = [AVAudioSession sharedInstance];<br />
AVAudioSessionRouteDescription *currentRoute = audioSession.currentRoute;<br />
for (AVAudioSessionPortDescription *outputPort in currentRoute.outputs) {<br />
if ([outputPort.portType isEqualToString:AVAudioSessionPortAirPlay]) {<br />
ottDict[@"ottStatus"] = @"1";<br />
ottDict[@"ottDeviceModel"] = outputPort.portName;<br />
ottDict[@"ottDeviceID"] = outputPort.UID;<br />
<br />
if ([outputPort.portName isEqualToString:@"AirPlay"]) {<br />
ottDict[@"ottDevice"] = @"airplay";<br />
ottDict[@"ottType"] = @"airplay";<br />
}<br />
else {<br />
if ([outputPort.portName containsString:@"Apple TV"]) {<br />
ottDict[@"ottDevice"] = @"appleTV";<br />
}<br />
else {<br />
ottDict[@"ottDevice"] = @"other";<br />
}<br />
<br />
if ([outputPort.UID hasSuffix:@"airplay"]) {<br />
ottDict[@"ottType"] = @"airplay";<br />
}<br />
else if ([outputPort.UID hasSuffix:@"screen"]) {<br />
ottDict[@"ottType"] = @"mirroring";<br />
}<br />
else {<br />
ottDict[@"ottType"] = @"other";<br />
}<br />
}<br />
}<br />
}<br />
<br />
// report OTT status update to Nielsen SDK<br />
[self reportOTTWithDict:ottDict];<br />
}<br />
</syntaxhighlight><br />
<br />
=== Report OTT update to the Nielsen SDK ===<br />
<syntaxhighlight lang="objective-c"><br />
- (void)reportOTTWithDict:(NSDictionary *)ottDict<br />
{<br />
[self.nielsenSDK updateOTT:ottDict];<br />
}<br />
</syntaxhighlight><br />
<br />
|Swift = <br />
<syntaxhighlight lang="swift"><br />
nielsenSdk.updateOTT(currentStatus)<br />
</syntaxhighlight><br />
<br />
=== Subscribe to AVAudioSessionRouteChangeNotification === <br />
<syntaxhighlight lang="swift"><br />
NotificationCenter.default.addObserver(self, selector: #selector(handleRouteChanged(_:)), name: NSNotification.Name.AVAudioSessionRouteChange, object: nil)<br />
</syntaxhighlight><br />
<br />
=== Handle AVAudioSessionRouteChangeNotification and prepare OTT dictionary:===<br />
<br />
<syntaxhighlight lang ="Swift"><br />
func handleRouteChanged(_ notification: Notification) {<br />
var currentStatus: [String: String] = ["ottStatus": "0"]<br />
<br />
let session = AVAudioSession.sharedInstance()<br />
let currentRoute = session.currentRoute<br />
for outputPort in currentRoute.outputs {<br />
if outputPort.portType == AVAudioSessionPortAirPlay {<br />
currentStatus["ottStatus"] = "1"<br />
currentStatus["ottDeviceModel"] = outputPort.portName<br />
currentStatus["ottDeviceID"] = outputPort.uid<br />
<br />
if outputPort.portName == "AirPlay" {<br />
currentStatus["ottDevice"] = "airplay"<br />
currentStatus["ottType"] = "airplay"<br />
}<br />
else {<br />
if outputPort.portName.contains("Apple TV") {<br />
currentStatus["ottDevice"] = "appleTV"<br />
}<br />
else {<br />
currentStatus["ottDevice"] = "other"<br />
}<br />
<br />
if outputPort.uid.hasSuffix("airplay") {<br />
currentStatus["ottType"] = "airplay"<br />
}<br />
else if outputPort.uid.hasSuffix("screen") {<br />
currentStatus["ottType"] = "mirroring"<br />
}<br />
else {<br />
currentStatus["ottType"] = "other"<br />
}<br />
}<br />
}<br />
}<br />
<br />
// report OTT status update to Nielsen SDK<br />
self.reportOTTUpdate(currentStatus)<br />
}<br />
</syntaxhighlight><br />
<br />
=== Report OTT update to the Nielsen SDK===<br />
<syntaxhighlight lang ="Swift"><br />
func reportOTTUpdate(_ ottDict: [String: String]) {<br />
if let nielsenSdk = self.nielsenAppApi {<br />
nielsenSdk.updateOTT(currentStatus)<br />
}<br />
}<br />
</syntaxhighlight><br />
}}<br />
<br />
== Going Live ==<br />
Following Nielsen testing, users need to make one update to the initialization call to ensure that the site is being measured properly.<br />
<br />
# '''Debug Logging''': Disable logging by deleting <code>{nol_sdkDebug: 'DEBUG'}</code> from initialization call.<br />
<br/><br />
'''Note''': before going live you have to inform Nielsen team - this is necessary, because Nielsen team has to adjust internal configuration parameter to enable data collection. Without that notification no data will be collected and no data will be reported.<br />
<br />
== Removing Simulators ==<br />
<br />
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. Here is an example Shell script that could be added as a Run Script phase in the application.<br />
<br />
<br />
<syntaxhighlight lang='bash'><br />
<br />
APP_PATH="${TARGET_BUILD_DIR}/${WRAPPER_NAME}"<br />
<br />
# This script loops through the frameworks embedded in the application and<br />
# removes unused architectures.<br />
find "$APP_PATH" -name '*.framework' -type d | while read -r FRAMEWORK<br />
do<br />
FRAMEWORK_EXECUTABLE_NAME=$(defaults read "$FRAMEWORK/Info.plist" CFBundleExecutable)<br />
FRAMEWORK_EXECUTABLE_PATH="$FRAMEWORK/$FRAMEWORK_EXECUTABLE_NAME"<br />
echo "Executable is $FRAMEWORK_EXECUTABLE_PATH"<br />
<br />
EXTRACTED_ARCHS=()<br />
<br />
for ARCH in $ARCHS<br />
do<br />
echo "Extracting $ARCH from $FRAMEWORK_EXECUTABLE_NAME"<br />
lipo -extract "$ARCH" "$FRAMEWORK_EXECUTABLE_PATH" -o "$FRAMEWORK_EXECUTABLE_PATH-$ARCH"<br />
EXTRACTED_ARCHS+=("$FRAMEWORK_EXECUTABLE_PATH-$ARCH")<br />
done<br />
<br />
echo "Merging extracted architectures: ${ARCHS}"<br />
lipo -o "$FRAMEWORK_EXECUTABLE_PATH-merged" -create "${EXTRACTED_ARCHS[@]}"<br />
rm "${EXTRACTED_ARCHS[@]}"<br />
<br />
echo "Replacing original executable with thinned version"<br />
rm "$FRAMEWORK_EXECUTABLE_PATH"<br />
mv "$FRAMEWORK_EXECUTABLE_PATH-merged" "$FRAMEWORK_EXECUTABLE_PATH"<br />
<br />
done<br />
</syntaxhighlight><br />
<br />
== Sample Applications ==<br />
The below sample applications have been designed to show the Simplified API's functionality and are broken into two distinct categories:<br />
* '''Basic''' - To show the functionality of the Nielsen Simplified API using a standard no-frills player.<br />
** [[Swift Basic Sample|Swift 4.0 Sample]]<br />
** [[Objective-c Basic example|Objective-C Sample]]<br />
** [[Android Basic example|Android Studio Example]]<br />
<br />
* '''Advanced''' - Nielsen Simplified API integrated into a custom video player.<br />
** [https://engineeringportal.nielsen.com/docs/Special:Downloads Swift 4.0 Sample]<br />
** [https://engineeringportal.nielsen.com/docs/Special:Downloads Objective-C Sample]<br />
** [https://engineeringportal.nielsen.com/docs/Special:Downloads Java/Android Studio Sample]</div>AlexGutierrezhttps://engineeringportal.nielsen.com//w/index.php?title=DCR_Video_Android_SDK&diff=3614DCR Video Android SDK2019-04-29T15:41:19Z<p>AlexGutierrez: Changes to Artifactory description to encourage use to Gradle-based implementation</p>
<hr />
<div>{{Breadcrumb|}} {{Breadcrumb|Digital}} {{Breadcrumb|DCR & DTVR}} {{CurrentBreadcrumb}}<br />
[[Category:Digital]]<br />
<br />
== Overview ==<br />
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.<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), and [[Digital Audio]]. Nielsen SDKs are also equipped to measure static content and can track key life cycle events of an application like:<br />
*Application launch events and how long app was running<br />
*Time of viewing a sub section / page in the application.<br />
<br />
== Prerequisites ==<br />
To start using the App SDK, the following details are required:<br />
* '''App ID (appid):''' Unique ID assigned to the player/site and configured by product.<br />
* '''sfcode:''' Unique identifier for the environment that the SDK should point to.<br />
* '''Nielsen SDK:''' The Nielsen SDK package contains a variety of sample players for your reference.<br />
If you do not have any of these prerequisites or if you have any questions, please contact our SDK sales support team.<br />
Refer to [[Digital Measurement Onboarding]] guide for information on how to get a Nielsen App SDK and appid.<br />
<br />
== Implementation ==<br />
This guide covers implementation steps for Android Studio utilizing the Standard Nielsen SDK for DCR.<br />
=== How to obtain the NielsenAppApi ===<br />
The Nielsen AppSDK can either be downloaded directly or can be integrated directly within an application through the use of Gradle. We recommend using the Gradle-based integration whenever possible to ensure you maintain the most recent changes and enhancements to the Nielsen libraries.<br />
* [[Digital_Measurement_Android_Artifactory_Guide|Select to obtain Gradle implementation guide]]<br />
* [[Special:Downloads|Select to Download Directly]]<br />
<br />
== Setting up your Development Environment ==<br />
<br />
=== Configuring Android Development Environment ===<br />
*The Nielsen App SDK (located in the [https://engineeringportal.nielsen.com/docs/Special:Downloads Downloads section] of the website) class is the primary application interface to the Nielsen App SDK on Android.<br />
*The Nielsen App SDK class is defined as the only public class belonging to the com.nielsen.app.sdk package.<br />
<br />
'''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.'''<br />
<br />
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.<br />
* '''For Video player applications'''<br />
** The Android OS hosting the App SDK should use a media player supporting HLS streaming (Android 3.0 and later will support it natively).<br />
** 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.<br />
* '''For Audio player applications'''<br />
** 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.<br />
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'''.<br />
Ensure the AppSdk.jar file is added in 'build.grade (App Level) file.<br />
* App SDK 1.2 provides support for x86, mips, and armeabi-7a architecture.<br />
<br />
==== Google Play Services ====<br />
Add the Google Play Services in the project,<br />
Steps: Android Studio -> File -> Project Structure ->(In module selection) select App -> Dependencies (tab) -> Click "+" button and select <code>"com.google.android.gms:play-services"</code>.<br />
Ensure it is added in build.gradle (App level) file<br />
<br />
==== Manifest File ==== <br />
* Add the following permissions on the project’s ''AndroidManifest.xml'' file.<br />
<syntaxhighlight lang="java"><br />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/><br />
<uses-permission android:name="android.permission.INTERNET"/></syntaxhighlight><br />
For more details to handle runtime permissions in Android versions, please visit [https://developer.android.com/training/permissions/requesting.html]. <br />
<br />
* In <code>AndroidManifest.xml </code>under <application> node add the following metadata<br />
<br />
<syntaxhighlight lang="java"><meta-data <br />
android:name="com.google.android.gms.version" <br />
android:value="@integer/google_play_services_version"/></syntaxhighlight><br />
<br />
* App SDK checks to see if there is a Google service available and updated.<br />
* 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.<br />
<br />
==== Library ====<br />
Nielsen App SDK uses the following packages/classes from the Google Play service.<br />
* google-play-services_lib<br />
<br />
==== Classes/package ====<br />
* com.google.android.gms.ads.identifier.AdvertisingIdClient;<br />
* com.google.android.gms.ads.identifier.AdvertisingIdClient.Info;<br />
* com.google.android.gms.common.ConnectionResult;<br />
* com.google.android.gms.common.GooglePlayServicesUtil;<br />
* com.google.android.gms.common.GooglePlayServicesRepairableException;<br />
* com.google.android.gms.common.GooglePlayServicesNotAvailableException;<br />
<br />
== SDK Initialization ==<br />
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)<br />
<br />
* A maximum of four SDK instances per appid are supported. <br />
* When four SDK instances exist, you must destroy an old instance before creating a new one.<br />
<br />
The following table contains the list of arguments that can be passed via the AppInfo JSON schema.<br />
<br />
* The appid is provided by the Nielsen Technical Account Manager (TAM). The appid is a GUID data type and is specific to the application.<br />
{| class="wikitable"<br />
|-<br />
! Parameter / Argument !! Description !! Source !! Required? !! Example<br />
|-<br />
| appid || Unique id for the application assigned by Nielsen. It is GUID data type.|| Nielsen-specified || Yes || PXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX<br />
|-<br />
| appname || Name of the application || Client-defined || Optional; automatically detected in SDK 6.0.0.4 and above || Nielsen Sample App<br />
|-<br />
| sfcode || Nielsen collection facility to which the SDK should connect.<br />
'''DTVR'''<br />
* "us"<br />
'''Digital Audio'''<br />
* "drm"<br />
'''DCR'''<br />
* "dcr" <br />
|| Nielsen-specified || Yes || dcr-cert<br />
|-<br />
|containerID || View ID of the UI element used as player view in application for Viewability ||Client-defined||Optional||"1234567"<br />
|-<br />
| nol_devDebug || Enables Nielsen console logging. Only required for testing<br />
|| Nielsen-specified || Optional || "DEBUG"<br />
|}<br />
<br />
== Debug flag for development environment ==<br />
Player application developers / integrators can use Debug flag to check whether an App SDK API call made is successful. To activate the Debug flag,<br />
Pass the argument <code>@"nol_devDebug":@"INFO"</code>, in the JSON string . The permitted values are:<br />
<br />
* '''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.<br />
* '''WARN''': Indicates potential integration / configuration errors or SDK issues.<br />
* '''ERROR''': Indicates important integration errors or non-recoverable SDK issues.<br />
* '''DEBUG''': Debug logs, used by the developers to debug more complex issues.<br />
<br />
Once the flag is active, it logs each API call made and the data passed. The log created by this flag is minimal.<br />
<blockquote>'''Note''': DO NOT activate the Debug flag in a production environment.</blockquote><br />
<br />
==== Sample SDK Initialization Code ====<br />
[[AppSDK()]] is no longer a singleton object and should be initialized as below.<br />
<br />
'''Initialization of App SDK object through a JSON object'''<br />
<syntaxhighlight lang="java"> <br />
<br />
try<br />
{<br />
// Prepare AppSdk configuration object (JSONObject)<br />
JSONObject appSdkConfig = new JSONObject()<br />
.put("appid", "PDA7D5EE6-B1B8-XXXX-XXXX-2A788BCXXXCA")<br />
.put("sfcode", "dcr")<br />
.put("containerID": "2131558561")<br />
.put("nol_devDebug", "DEBUG"); // only for debug builds<br />
<br />
// Pass appSdkConfig to the AppSdk constructor<br />
mAppSdk = new AppSdk(appContext, appSdkConfig, appSdkListener);<br />
}<br />
catch (JSONException e)<br />
{<br />
Log.e(TAG, "Couldn’t prepare JSONObject for appSdkConfig", e);<br />
}<br />
</syntaxhighlight><br />
Here, <code>appContext</code> is the App context object and <code>appSdkConfig</code> is JSON object for holding the parameters (<code>appid</code>, <code>sfcode</code>) the App passes to the Nielsen App SDK via a JSON string. The appid is obtained from Nielsen operational support and is unique to the app.<br />
<br />
<br />
The integration of Nielsen App SDK will depend on type of client app.<br /><br />
* Ensure that SDK files (AppSdk.jar and libAppSdk.so [App SDK 1.2 Only]) are included under the App’s project and the App SDK is linked to the App (the setting to link App SDK to the App can be found on property page of the App’s project).<br />
<br />
<!-- == Initializing the Nielsen AppSDK to measure the Viewability ==<br />
The integrator to support the viewability metrics in the application has to provide a tag value of the player view to let Nielsen AppSDK know that there is a player that needs to be tracked. It’s called the ‘containerId’ and it should be passed in application info dictionary as string while initializing the Nielsen AppSDK.<br />
<br />
==== Android ====<br />
{| class="wikitable"<br />
|-<br />
! # !! Parameter Name !! Description !! Supported Values !! Example<br />
|-<br />
| 1 || containerId ||View ID of the UI element used as player view in application. getId() method of View class can be used to get this value. ||A positive integer used to identify the view. || 2131558561<br />
|}<br />
<br />
==== iOS ====<br />
{| class="wikitable"<br />
|-<br />
! # !! Parameter Name !! Description !! Supported Values !! Example<br />
|-<br />
| 1 || containerId ||The tag of the UIView that represents the Player View ||The string value representing the NSInteger value with maximum value of NSIntegerMax that is related on 32- or 64-bit applications. || "100" <br> "2131558561"<br />
|}<br />
<br />
For iOS it is required to link additional frameworks that are needed for viewability engine:<br><br />
<code>JavaScriptCore.framework</code> <br><br />
<code>WebKit.framework</code><br />
<br />
The Nielsen AppSDK uses a tracking WebView (TWV) approach. For more information on Viewability, please refer to [https://engineeringportal.nielsen.com/docs/Implementing_Viewability_with_AppSDK Implementing Viewability with AppSDK.]<br />
--><br />
<br />
== APP SDK Error & Event Codes ==<br />
To view the Error and Event codes for iOS and Android, please review the [[APP SDK Event Codes|App SDK Event Code]] Reference page.<br />
<br />
== Configure Payload ==<br />
=== Handling JSON Metadata ===<br />
All the SDK methods handles only two types of objects: NSString, NSDictionary. The parameters passed must be either a JSON formatted string or a NSDictionary object. The JSON passed in the SDK must be well-formed.<br />
* NSDictionary object<br />
** If an object of unexpected type is passed to the method, the error message will be logged.<br />
** If string has invalid JSON format, the error message will be logged.<br />
* JSON value must be string value.<br />
** This includes boolean and numeric values. For example, a value of true should be represented with "true", number value 123 should be "123".<br />
** All the Variable Names like appid, appname, sfcode, dataSrc, title, type etc. are case-sensitive. Use the correct variable name as specified in the documentation.<br />
* JSON string can be prepared using either raw NSString or serialized NSDictionary.<br />
<syntaxhighlight lang="java"><br />
JSONObject contentMetadata = new JSONObject()<br />
//SDK Metadata<br />
.put("type", "content")<br />
.put("assetid", "vid345-67483")<br />
.put("program", "Program Name")<br />
.put("title", "Program S3, EP1")<br />
.put("length", "3600")<br />
.put("segB", "CustomSegmentValueB") //optional<br />
.put("segC", "CustomSegmentValueC") //optional<br />
.put("isfullepisode", "yes")<br />
.put("airdate", "20161013 20:00:00")<br />
</syntaxhighlight><br />
<br />
=== Content metadata ===<br />
Content metadata should remain constant throughout the entirety of an episode/clip including when ads play.<br />
{{DCR Content Metadata}}<br />
<br />
=== Ad Metadata ===<br />
The ad metadata (if applicable) should be passed for each individual ad, if ads are available during or before the stream begins.<br />
<br/><br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| type || type of ad || 'preroll', 'midroll', or 'postroll' || ✓<br />
|-<br />
| assetid || unique ID assigned to ad || custom || ✓<br />
|}<br />
== Sequence of Calls ==<br />
=== play ===<br />
Use [[DCR_Video_APP_SDK#play|play]] to pass the channel descriptor information through channelName parameter when the user taps the '''Play''' button on the player.<br />
<syntaxhighlight lang="java"> public void play(JSONObject channelInfo);</syntaxhighlight><br />
<br />
=== loadMetadata ===<br />
<syntaxhighlight lang="java">public void loadMetadata(JSONObject contentMetadata);</syntaxhighlight><br />
<br />
=== playheadPosition ===<br />
<syntaxhighlight lang="java"><br />
public void setPlayheadPosition(long position)<br />
</syntaxhighlight><br />
<br />
=== stop ===<br />
<syntaxhighlight lang="java">public void stop()</syntaxhighlight><br />
<br />
=== end ===<br />
When content stop is initiated and content cannot be resumed from the same position (it can only be restarted from the beginning of stream).<br />
<syntaxhighlight lang="java">public void end()</syntaxhighlight><br />
<br />
=== Example Ad Object ===<br />
<syntaxhighlight lang="javascript"><br />
// create ad object<br />
"ad": {<br />
"type": "preroll",<br />
"assetid": "AD-ID123"<br />
}<br />
</syntaxhighlight><br />
<br />
== Configure API Calls ==<br />
<br />
=== Sample API Sequence ===<br />
A Sample API sequence could follow this flow:<br />
{| class="wikitable"<br />
|-<br />
! Type !! Sample code !! Description<br />
|-<br />
|On App Start||<code>[nielsenMeter loadMetadata: contentMetadata];</code> || // contentMetadata Object contains the JSON metadata for the impression<br />
|-<br />
| rowspan="2" | Start of stream || <code>[nielsenMeter play: channelName];</code> || // channelName contains JSON metadata of channel/video name being played<br />
|-<br />
| <code>[nielsenMeter loadMetadata: contentMetadataObject];</code> || // contentMetadataObject contains the JSON metadata for the content being played<br />
|-<br />
| Content || <code>[nielsenMeter setplayheadPosition: position];</code> || // playheadPosition is position of the playhead while the content is being played<br />
|-<br />
| End of Stream || <code>[nielsenMeter end];</code> || // Content playback is completed.<br />
|}<br />
<br />
=== SDK Events ===<br />
{| class="wikitable"<br />
|-<br />
! Event !! Parameter !! Description<br />
|-<br />
| 'loadMetadata' || content/ad metadata object || Needs to be called at the beginning of each asset<br />
|-<br />
| 'setPlayheadPosition' || playhead position as integer<br/><br />
VOD: || current position in seconds <br/><br />
Live: current Unix timestamp (seconds since Jan-1-1970 UTC) <br/><br />
Note: 'setPlayheadPosition' has to be called every second<br />
||<br />
Pass playhead position every second during playback<br />
|-<br />
| 'stop' || playhead position || Call when content or ads complete playing and pass playhead position<br />
|-<br />
| 'end' || playhead position in seconds || Call when the current video asset completes playback and pass the playhead position. <br/><br />
Example: At the end of the content stream, if the user switches to another piece of content, when the browser is refreshed or closed.<br />
|}<br />
<blockquote>Note: For livestream, send the Unix timestamp; for VOD send the time in seconds as integer. The final playhead position must be sent for the current asset being played before calling <code>'''stop'''</code>, <code>'''end'''</code> or<code> '''loadmetadata'''</code>,.</blockquote><br />
<br />
=== Life cycle of SDK instance ===<br />
Life cycle of SDK instance includes four general states:<br />
# '''Initial state''' – The SDK is not initialized and hence, not ready to process playing information. Once the SDK is moved out of this state, it needs instantiation of the new SDK instance in order to get the instance in the '''Initial state'''.<br />
# '''Idle state''' – The SDK is initialized and is ready to process playing information. Once Initialized, the SDK instance is not processing any data, but is listening for the play event to occur.<br />
# '''Processing state''' – The SDK instance is processing playing information. The <code>'''play'''</code> and <code>'''loadMetadata''' </code> calls move the SDK instance into this state. In this state, the SDK instance will be able to process the following calls.<br />
## <code>'''setplayheadPosition'''</code> – Call this API every one second when playhead position timer is fired. If a LIVE event, use the current Unix timestamp (seconds since Jan-1-1970 UTC).<br />
## <code>'''stop'''</code> – Call this API when the playback is paused, switches between content and ad (within the same content playback) or encounters interruptions.<br />
## <code>'''end'''</code> – SDK instance exits from Processing state when this API is called.<br />
# '''Disabled state''' – The SDK instance is disabled and is not processing playing information. SDK instance moves into this state in one of the following scenarios.<br />
## Initialization fails<br />
## <code>'''appDisableApi'''</code> is set to <code>true</code><br />
<br />
<blockquote>'''Note:''' For API Version 5.1 and above, App SDK will fire data pings and continue measurement even after the user has opted out from Nielsen measurement on a device. The data ping will be marked as opted-out ping.<br />
<br />
'''Note''': In case of any interruptions during playback due to alarm, calendar, call, flight mode, Wi-Fi toggle, channel change, etc., call [[stop]] to stop the measurement.<br />
* As soon as the playback resumes, call <code>'''play'''</code>, <code>'''loadMetadata''' </code> and <code>'''playheadPosition'''</code> </blockquote><br />
<br />
=== API Call Sequence ===<br />
==== Use Case 1: Content has no Advertisements ====<br />
Call [[play()]] with channelName JSON as below.<br />
<syntaxhighlight lang="json">{<br />
"channelName": "TheMovieTitle"<br />
}</syntaxhighlight><br />
Call [[loadMetadata()]] with JSON metadata for content as below.<br />
<syntaxhighlight lang="json">{<br />
"type": "content",<br />
"assetid": "vid345-67483",<br />
"program": "ProgramName",<br />
"title": "Program S3, EP1",<br />
"length": "3600",<br />
...<br />
}</syntaxhighlight><br />
Call [[setPlayheadPosition()]] every one second until a pause / stop.<br />
Use the sample API sequence below as a reference to identify the specific events that need to be called during content playback without ads.<br />
{| class="wikitable"<br />
|-<br />
! Type !! Sample code !! Description<br />
|-<br />
| rowspan="2" | Start of stream || <code>mAppSdk.play(channelName); </code> || // channelName contains JSON metadata of channel/video name being played<br />
|-<br />
| <code>mAppSdk.loadMetadata(contentMetaDataObject);</code> || // contentMetadataObject contains the JSON metadata for the content being played<br />
|-<br />
| Content || <code>mAppSdk.setPlayheadPosition(playheadPosition);</code> || // position is position of the playhead while the content is being played<br />
|-<br />
| End of Stream || <code>mAppSdk.end();</code> || // Content playback is completed.<br />
|}<br />
<br />
==== Use Case 2: Content has Advertisements ====<br />
Call [[play()]] with channelName JSON as below.<br />
<syntaxhighlight lang="json">{<br />
"channelName": "TheMovieTitle"<br />
}</syntaxhighlight><br />
Call [[loadMetadata()]] with JSON metadata for ad as below.<br />
<syntaxhighlight lang="json">{<br />
"type": "preroll",<br />
"assetid": "ad=123"<br />
}</syntaxhighlight><br />
<blockquote>Note: In case the individual ad details are not available, send ad pod (presence) details through the [[loadMetadata]] and playhead position through [[playheadPosition]].</blockquote><br />
<br />
Call [[setPlayheadPosition()]] every one second until a pause / stop / another [[loadMetadata()]] is called. Playhead should be passed for the entire duration of ad pod, if the ad pod details are passed as part of [[loadMetadata()]].<br />
<br />
'''Ad Content'''<br />
<syntaxhighlight lang="java"> long pos = mAdPlayer.videoPosition() / 1000;<br />
if (mAppSdk != null)<br />
{<br />
mAppSdk.setPlayheadPosition(pos);<br />
}</syntaxhighlight><br />
<blockquote>Note: The playhead positions for ad and content should be maintained separately.</blockquote><br />
<br />
The sample API sequence can be used as a reference to identify the specific events that need to be called during content and ad playback.<br />
{| class="wikitable"<br />
|-<br />
! Type !! Sample code !! Description<br />
|-<br />
| rowspan="2" | Start of stream || <code>mAppSdk.play(channelName); </code> || // channelName contains JSON metadata of channel/video name being played<br />
|-<br />
| <code>mAppSdk.loadMetadata(contentMetaDataObject);</code> || // contentMetadataObject contains the JSON metadata for the content being played<br />
|-<br />
| rowspan="3" | Preroll || <code>mAppSdk.loadMetadata(prerollMetadataObject);</code> || // prerollMetadataObject contains the JSON metadata for the preroll ad<br />
|-<br />
| <code>mAppSdk.setPlayheadPosition(playheadPosition);</code> || // position is position of the playhead while the preroll ad is being played<br />
|-<br />
| <code>mAppSdk.stop();</code> || // Call stop after preroll occurs<br />
|-<br />
| rowspan="3" | Content || <code>mAppSdk.loadMetadata(contentMetaDataObject);</code> || // contentMetadataObject contains the JSON metadata for the content being played<br />
|-<br />
| <code>mAppSdk.setPlayheadPosition(playheadPosition);</code> || // position is position of the playhead while the content is being played<br />
|-<br />
| <code>mAppSdk.stop();</code> || // Call stop after the content is paused (ad starts)<br />
|-<br />
| rowspan="6" | Midroll || <code>mAppSdk.loadMetadata(midrollMetaDataObject);</code> || // midrollMetadataObject contains the JSON metadata for the midroll ad<br />
|-<br />
| <code>mAppSdk.setPlayheadPosition(playheadPosition);</code> || // position is position of the playhead while the midroll ad is being played<br />
|-<br />
| <code>mAppSdk.stop();</code> || // App moves to background(midroll pauses) <br />
|-<br />
| <code>mAppSdk.loadMetadata(midrollMetaDataObject);</code> || // App moves to foreground (midroll resumes) <br />
|-<br />
| <code>mAppSdk.setPlayheadPosition(playheadPosition);</code> || // playheadPosition is position of the playhead while the midroll ad is being played <br />
|-<br />
| <code>mAppSdk.stop();</code> || // Call stop after midroll occurs<br />
|-<br />
| rowspan="3" | Content (End of stream) || <code>mAppSdk.loadMetadata(contentMetaDataObject);</code> || // contentMetadataObject contains the JSON metadata for the content being played<br />
|-<br />
| <code>mAppSdk.setPlayheadPosition(playheadPosition);</code> || // position is position of the playhead while the content is being played<br />
|-<br />
| <code>mAppSdk.stop();</code> || // Always call stop irrespective of postroll is followed or not<br />
|-<br />
| End of Stream || <code>mAppSdk.end();</code> || // Call end() at the end of content<br />
|-<br />
| rowspan="3" | Postroll || <code>mAppSdk.loadMetadata(postrollMetaDataObject);</code> || // postrollMetadataObject contains the JSON metadata for the postroll ad<br />
|-<br />
| <code>mAppSdk.setPlayheadPosition(playheadPosition);</code> || // position is position of the playhead while the postroll ad is being played<br />
|-<br />
| <code>mAppSdk.stop();</code> || // Call stop after postroll occurs<br />
|}<br />
<br />
<blockquote>Note: Each Ad playhead should reset or begin from 0 at ad start. When content has resumed following an ad break, playhead position must continue from where previous content segment was left off.</blockquote><br />
<br />
== Handling Foreground and Background states ==<br />
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<br />
* Enable the Nielsen SDK to measure background/foreground state by makingthe relevant update to the AndroidManifest.<br />
* Integrate Nielsen’s SdkBgFgDetectionUtility class within your Custom Application Class.<br />
* Custom implementation of the required methods within your application.<br />
<br />
=== ForeGround/Background Measurement via AndroidManifest ===<br />
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.<br />
<syntaxhighlight lang="java"><br />
<application android:name="com.nielsen.app.sdk.AppSdkApplication"><br />
</syntaxhighlight><br />
<br />
=== Using the Android SdkBgFbDetectionUtility Class ===<br />
For developers who are already using the application class, it is recommended that background/foreground state is implemented using the [https://engineeringportal.nielsen.com/docs/Android_Background_Foreground SdkBgFgDetectionUtility class]. The [https://engineeringportal.nielsen.com/docs/Android_Background_Foreground SdkBgFgDetectionUtility class] is compatible with Android 4+ and has been made available to Nielsen clients. (You will need to copy/paste the code provided into a file).<br />
<br />
=== Manual Background/ForeGround State Management ===<br />
In cases where the developer is not able to use the AndroidManifest.xml solution nor the Nielsen provided [https://engineeringportal.nielsen.com/docs/Android_Background_Foreground 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.<br />
<br />
The SDK is informed about app state using the below methods.<br />
<syntaxhighlight lang="java"><br />
AppLaunchMeasurementManager.appInForeground(getApplicationContext());<br />
AppLaunchMeasurementManager.appInBackground(getApplicationContext());<br />
</syntaxhighlight><br />
Within the lifecycle of individual activities, onResume() and onPause() are best suited to providing indication of the app state.<br />
<br />
<br />
Correct measurement of the foreground/background state is crucial to Static App measurement within Nielsen Digital Content Ratings (DCR).<br />
<br />
== Interruptions during playback ==<br />
As part of integrating Nielsen App SDK with the player application, the Audio / Video app developer needs to handle the following possible interruption scenarios:<br />
* Pause / Play<br />
* Network Loss (Wi-Fi / Airplane / Cellular)<br />
* Call Interrupt (SIM or Third party Skype / Hangout call)<br />
* Alarm Interrupt<br />
* Content Buffering<br />
* Device Lock / Unlock (Video players only, not for Audio players)<br />
* App going in the Background/Foreground (Video players only, not for Audio players)<br />
* Channel / Station Change Scenario<br />
* Unplugging of headphone<br />
In case of encountering one of the above interruptions, the player application needs to<br />
* Call [[stop]] immediately (except when content is buffering) and withhold sending playhead position.<br />
* Start sending pings – <code>'loadMetadata'</code> and <code>'playheadPosition'</code> for the new viewing session, once the playback resumes.<br />
Please see the [https://engineeringportal.nielsen.com/docs/Digital_Measurement_Interruption_Scenarios Interruption Scenarios Page] for more details<br />
<br />
== Pre-Certification Checklists ==<br />
After the application is ready to be sent for Nielsen Certification, please go through the [[Digital Pre-Certification Checklist App SDK]] and ensure the app behaves as expected, before submitting to Nielsen.<br />
<br />
{{Template:Android_Privacy_and_Opt-Out}}<br />
<br />
== Going Live ==<br />
Following Nielsen testing, users need to make one update to the initialization call to ensure that the site is being measured properly.<br />
<br />
# '''Debug Logging''': Disable logging by deleting <code>{nol_sdkDebug: 'DEBUG'}</code> from initialization call.<br />
'''Note''': before going live you have to inform Nielsen team - this is necessary, because Nielsen team has to adjust internal configuration parameter to enable data collection. Without that notification no data will be collected and no data will be reported.<br />
<br />
== Sample Applications ==<br />
The below sample applications have been designed to show the API's functionality and are broken into two distinct categories:<br />
* '''Basic''' - To show the functionality of the Nielsen API using a standard no-frills player.<br />
** [[Swift Basic Sample|Swift 4.0 Sample]]<br />
** [[Objective-c Basic example|Objective-C Sample]]<br />
** [[Android Basic example|Android Studio Example]]<br />
<br />
* '''Advanced''' - Nielsen API integrated into a custom video player is bundled with the SDK.</div>AlexGutierrezhttps://engineeringportal.nielsen.com//w/index.php?title=DCR_Video_Android_SDK&diff=3613DCR Video Android SDK2019-04-29T15:31:53Z<p>AlexGutierrez: </p>
<hr />
<div>{{Breadcrumb|}} {{Breadcrumb|Digital}} {{Breadcrumb|DCR & DTVR}} {{CurrentBreadcrumb}}<br />
[[Category:Digital]]<br />
<br />
== Overview ==<br />
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.<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), and [[Digital Audio]]. Nielsen SDKs are also equipped to measure static content and can track key life cycle events of an application like:<br />
*Application launch events and how long app was running<br />
*Time of viewing a sub section / page in the application.<br />
<br />
== Prerequisites ==<br />
To start using the App SDK, the following details are required:<br />
* '''App ID (appid):''' Unique ID assigned to the player/site and configured by product.<br />
* '''sfcode:''' Unique identifier for the environment that the SDK should point to.<br />
* '''Nielsen SDK:''' The Nielsen SDK package contains a variety of sample players for your reference.<br />
If you do not have any of these prerequisites or if you have any questions, please contact our SDK sales support team.<br />
Refer to [[Digital Measurement Onboarding]] guide for information on how to get a Nielsen App SDK and appid.<br />
<br />
== Implementation ==<br />
This guide covers implementation steps for Android Studio utilizing the Standard Nielsen SDK for DCR.<br />
=== How to obtain the NielsenAppApi ===<br />
The Nielsen AppSDK can either be downloaded directly or can be integrated directly within an application through the use of a CocoaPod or Gradle. We recommend using the dependency manager-based integration whenever possible to ensure you have the most recent changes and enhancements.<br />
* [[Special:Downloads|Select to Download Directly]]<br />
* [[Digital_Measurement_Android_Artifactory_Guide|Select to obtain Gradle implementation guide]]<br />
<br />
== Setting up your Development Environment ==<br />
<br />
=== Configuring Android Development Environment ===<br />
*The Nielsen App SDK (located in the [https://engineeringportal.nielsen.com/docs/Special:Downloads Downloads section] of the website) class is the primary application interface to the Nielsen App SDK on Android.<br />
*The Nielsen App SDK class is defined as the only public class belonging to the com.nielsen.app.sdk package.<br />
<br />
'''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.'''<br />
<br />
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.<br />
* '''For Video player applications'''<br />
** The Android OS hosting the App SDK should use a media player supporting HLS streaming (Android 3.0 and later will support it natively).<br />
** 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.<br />
* '''For Audio player applications'''<br />
** 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.<br />
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'''.<br />
Ensure the AppSdk.jar file is added in 'build.grade (App Level) file.<br />
* App SDK 1.2 provides support for x86, mips, and armeabi-7a architecture.<br />
<br />
==== Google Play Services ====<br />
Add the Google Play Services in the project,<br />
Steps: Android Studio -> File -> Project Structure ->(In module selection) select App -> Dependencies (tab) -> Click "+" button and select <code>"com.google.android.gms:play-services"</code>.<br />
Ensure it is added in build.gradle (App level) file<br />
<br />
==== Manifest File ==== <br />
* Add the following permissions on the project’s ''AndroidManifest.xml'' file.<br />
<syntaxhighlight lang="java"><br />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/><br />
<uses-permission android:name="android.permission.INTERNET"/></syntaxhighlight><br />
For more details to handle runtime permissions in Android versions, please visit [https://developer.android.com/training/permissions/requesting.html]. <br />
<br />
* In <code>AndroidManifest.xml </code>under <application> node add the following metadata<br />
<br />
<syntaxhighlight lang="java"><meta-data <br />
android:name="com.google.android.gms.version" <br />
android:value="@integer/google_play_services_version"/></syntaxhighlight><br />
<br />
* App SDK checks to see if there is a Google service available and updated.<br />
* 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.<br />
<br />
==== Library ====<br />
Nielsen App SDK uses the following packages/classes from the Google Play service.<br />
* google-play-services_lib<br />
<br />
==== Classes/package ====<br />
* com.google.android.gms.ads.identifier.AdvertisingIdClient;<br />
* com.google.android.gms.ads.identifier.AdvertisingIdClient.Info;<br />
* com.google.android.gms.common.ConnectionResult;<br />
* com.google.android.gms.common.GooglePlayServicesUtil;<br />
* com.google.android.gms.common.GooglePlayServicesRepairableException;<br />
* com.google.android.gms.common.GooglePlayServicesNotAvailableException;<br />
<br />
== SDK Initialization ==<br />
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)<br />
<br />
* A maximum of four SDK instances per appid are supported. <br />
* When four SDK instances exist, you must destroy an old instance before creating a new one.<br />
<br />
The following table contains the list of arguments that can be passed via the AppInfo JSON schema.<br />
<br />
* The appid is provided by the Nielsen Technical Account Manager (TAM). The appid is a GUID data type and is specific to the application.<br />
{| class="wikitable"<br />
|-<br />
! Parameter / Argument !! Description !! Source !! Required? !! Example<br />
|-<br />
| appid || Unique id for the application assigned by Nielsen. It is GUID data type.|| Nielsen-specified || Yes || PXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX<br />
|-<br />
| appname || Name of the application || Client-defined || Optional; automatically detected in SDK 6.0.0.4 and above || Nielsen Sample App<br />
|-<br />
| sfcode || Nielsen collection facility to which the SDK should connect.<br />
'''DTVR'''<br />
* "us"<br />
'''Digital Audio'''<br />
* "drm"<br />
'''DCR'''<br />
* "dcr" <br />
|| Nielsen-specified || Yes || dcr-cert<br />
|-<br />
|containerID || View ID of the UI element used as player view in application for Viewability ||Client-defined||Optional||"1234567"<br />
|-<br />
| nol_devDebug || Enables Nielsen console logging. Only required for testing<br />
|| Nielsen-specified || Optional || "DEBUG"<br />
|}<br />
<br />
== Debug flag for development environment ==<br />
Player application developers / integrators can use Debug flag to check whether an App SDK API call made is successful. To activate the Debug flag,<br />
Pass the argument <code>@"nol_devDebug":@"INFO"</code>, in the JSON string . The permitted values are:<br />
<br />
* '''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.<br />
* '''WARN''': Indicates potential integration / configuration errors or SDK issues.<br />
* '''ERROR''': Indicates important integration errors or non-recoverable SDK issues.<br />
* '''DEBUG''': Debug logs, used by the developers to debug more complex issues.<br />
<br />
Once the flag is active, it logs each API call made and the data passed. The log created by this flag is minimal.<br />
<blockquote>'''Note''': DO NOT activate the Debug flag in a production environment.</blockquote><br />
<br />
==== Sample SDK Initialization Code ====<br />
[[AppSDK()]] is no longer a singleton object and should be initialized as below.<br />
<br />
'''Initialization of App SDK object through a JSON object'''<br />
<syntaxhighlight lang="java"> <br />
<br />
try<br />
{<br />
// Prepare AppSdk configuration object (JSONObject)<br />
JSONObject appSdkConfig = new JSONObject()<br />
.put("appid", "PDA7D5EE6-B1B8-XXXX-XXXX-2A788BCXXXCA")<br />
.put("sfcode", "dcr")<br />
.put("containerID": "2131558561")<br />
.put("nol_devDebug", "DEBUG"); // only for debug builds<br />
<br />
// Pass appSdkConfig to the AppSdk constructor<br />
mAppSdk = new AppSdk(appContext, appSdkConfig, appSdkListener);<br />
}<br />
catch (JSONException e)<br />
{<br />
Log.e(TAG, "Couldn’t prepare JSONObject for appSdkConfig", e);<br />
}<br />
</syntaxhighlight><br />
Here, <code>appContext</code> is the App context object and <code>appSdkConfig</code> is JSON object for holding the parameters (<code>appid</code>, <code>sfcode</code>) the App passes to the Nielsen App SDK via a JSON string. The appid is obtained from Nielsen operational support and is unique to the app.<br />
<br />
<br />
The integration of Nielsen App SDK will depend on type of client app.<br /><br />
* Ensure that SDK files (AppSdk.jar and libAppSdk.so [App SDK 1.2 Only]) are included under the App’s project and the App SDK is linked to the App (the setting to link App SDK to the App can be found on property page of the App’s project).<br />
<br />
<!-- == Initializing the Nielsen AppSDK to measure the Viewability ==<br />
The integrator to support the viewability metrics in the application has to provide a tag value of the player view to let Nielsen AppSDK know that there is a player that needs to be tracked. It’s called the ‘containerId’ and it should be passed in application info dictionary as string while initializing the Nielsen AppSDK.<br />
<br />
==== Android ====<br />
{| class="wikitable"<br />
|-<br />
! # !! Parameter Name !! Description !! Supported Values !! Example<br />
|-<br />
| 1 || containerId ||View ID of the UI element used as player view in application. getId() method of View class can be used to get this value. ||A positive integer used to identify the view. || 2131558561<br />
|}<br />
<br />
==== iOS ====<br />
{| class="wikitable"<br />
|-<br />
! # !! Parameter Name !! Description !! Supported Values !! Example<br />
|-<br />
| 1 || containerId ||The tag of the UIView that represents the Player View ||The string value representing the NSInteger value with maximum value of NSIntegerMax that is related on 32- or 64-bit applications. || "100" <br> "2131558561"<br />
|}<br />
<br />
For iOS it is required to link additional frameworks that are needed for viewability engine:<br><br />
<code>JavaScriptCore.framework</code> <br><br />
<code>WebKit.framework</code><br />
<br />
The Nielsen AppSDK uses a tracking WebView (TWV) approach. For more information on Viewability, please refer to [https://engineeringportal.nielsen.com/docs/Implementing_Viewability_with_AppSDK Implementing Viewability with AppSDK.]<br />
--><br />
<br />
== APP SDK Error & Event Codes ==<br />
To view the Error and Event codes for iOS and Android, please review the [[APP SDK Event Codes|App SDK Event Code]] Reference page.<br />
<br />
== Configure Payload ==<br />
=== Handling JSON Metadata ===<br />
All the SDK methods handles only two types of objects: NSString, NSDictionary. The parameters passed must be either a JSON formatted string or a NSDictionary object. The JSON passed in the SDK must be well-formed.<br />
* NSDictionary object<br />
** If an object of unexpected type is passed to the method, the error message will be logged.<br />
** If string has invalid JSON format, the error message will be logged.<br />
* JSON value must be string value.<br />
** This includes boolean and numeric values. For example, a value of true should be represented with "true", number value 123 should be "123".<br />
** All the Variable Names like appid, appname, sfcode, dataSrc, title, type etc. are case-sensitive. Use the correct variable name as specified in the documentation.<br />
* JSON string can be prepared using either raw NSString or serialized NSDictionary.<br />
<syntaxhighlight lang="java"><br />
JSONObject contentMetadata = new JSONObject()<br />
//SDK Metadata<br />
.put("type", "content")<br />
.put("assetid", "vid345-67483")<br />
.put("program", "Program Name")<br />
.put("title", "Program S3, EP1")<br />
.put("length", "3600")<br />
.put("segB", "CustomSegmentValueB") //optional<br />
.put("segC", "CustomSegmentValueC") //optional<br />
.put("isfullepisode", "yes")<br />
.put("airdate", "20161013 20:00:00")<br />
</syntaxhighlight><br />
<br />
=== Content metadata ===<br />
Content metadata should remain constant throughout the entirety of an episode/clip including when ads play.<br />
{{DCR Content Metadata}}<br />
<br />
=== Ad Metadata ===<br />
The ad metadata (if applicable) should be passed for each individual ad, if ads are available during or before the stream begins.<br />
<br/><br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| type || type of ad || 'preroll', 'midroll', or 'postroll' || ✓<br />
|-<br />
| assetid || unique ID assigned to ad || custom || ✓<br />
|}<br />
== Sequence of Calls ==<br />
=== play ===<br />
Use [[DCR_Video_APP_SDK#play|play]] to pass the channel descriptor information through channelName parameter when the user taps the '''Play''' button on the player.<br />
<syntaxhighlight lang="java"> public void play(JSONObject channelInfo);</syntaxhighlight><br />
<br />
=== loadMetadata ===<br />
<syntaxhighlight lang="java">public void loadMetadata(JSONObject contentMetadata);</syntaxhighlight><br />
<br />
=== playheadPosition ===<br />
<syntaxhighlight lang="java"><br />
public void setPlayheadPosition(long position)<br />
</syntaxhighlight><br />
<br />
=== stop ===<br />
<syntaxhighlight lang="java">public void stop()</syntaxhighlight><br />
<br />
=== end ===<br />
When content stop is initiated and content cannot be resumed from the same position (it can only be restarted from the beginning of stream).<br />
<syntaxhighlight lang="java">public void end()</syntaxhighlight><br />
<br />
=== Example Ad Object ===<br />
<syntaxhighlight lang="javascript"><br />
// create ad object<br />
"ad": {<br />
"type": "preroll",<br />
"assetid": "AD-ID123"<br />
}<br />
</syntaxhighlight><br />
<br />
== Configure API Calls ==<br />
<br />
=== Sample API Sequence ===<br />
A Sample API sequence could follow this flow:<br />
{| class="wikitable"<br />
|-<br />
! Type !! Sample code !! Description<br />
|-<br />
|On App Start||<code>[nielsenMeter loadMetadata: contentMetadata];</code> || // contentMetadata Object contains the JSON metadata for the impression<br />
|-<br />
| rowspan="2" | Start of stream || <code>[nielsenMeter play: channelName];</code> || // channelName contains JSON metadata of channel/video name being played<br />
|-<br />
| <code>[nielsenMeter loadMetadata: contentMetadataObject];</code> || // contentMetadataObject contains the JSON metadata for the content being played<br />
|-<br />
| Content || <code>[nielsenMeter setplayheadPosition: position];</code> || // playheadPosition is position of the playhead while the content is being played<br />
|-<br />
| End of Stream || <code>[nielsenMeter end];</code> || // Content playback is completed.<br />
|}<br />
<br />
=== SDK Events ===<br />
{| class="wikitable"<br />
|-<br />
! Event !! Parameter !! Description<br />
|-<br />
| 'loadMetadata' || content/ad metadata object || Needs to be called at the beginning of each asset<br />
|-<br />
| 'setPlayheadPosition' || playhead position as integer<br/><br />
VOD: || current position in seconds <br/><br />
Live: current Unix timestamp (seconds since Jan-1-1970 UTC) <br/><br />
Note: 'setPlayheadPosition' has to be called every second<br />
||<br />
Pass playhead position every second during playback<br />
|-<br />
| 'stop' || playhead position || Call when content or ads complete playing and pass playhead position<br />
|-<br />
| 'end' || playhead position in seconds || Call when the current video asset completes playback and pass the playhead position. <br/><br />
Example: At the end of the content stream, if the user switches to another piece of content, when the browser is refreshed or closed.<br />
|}<br />
<blockquote>Note: For livestream, send the Unix timestamp; for VOD send the time in seconds as integer. The final playhead position must be sent for the current asset being played before calling <code>'''stop'''</code>, <code>'''end'''</code> or<code> '''loadmetadata'''</code>,.</blockquote><br />
<br />
=== Life cycle of SDK instance ===<br />
Life cycle of SDK instance includes four general states:<br />
# '''Initial state''' – The SDK is not initialized and hence, not ready to process playing information. Once the SDK is moved out of this state, it needs instantiation of the new SDK instance in order to get the instance in the '''Initial state'''.<br />
# '''Idle state''' – The SDK is initialized and is ready to process playing information. Once Initialized, the SDK instance is not processing any data, but is listening for the play event to occur.<br />
# '''Processing state''' – The SDK instance is processing playing information. The <code>'''play'''</code> and <code>'''loadMetadata''' </code> calls move the SDK instance into this state. In this state, the SDK instance will be able to process the following calls.<br />
## <code>'''setplayheadPosition'''</code> – Call this API every one second when playhead position timer is fired. If a LIVE event, use the current Unix timestamp (seconds since Jan-1-1970 UTC).<br />
## <code>'''stop'''</code> – Call this API when the playback is paused, switches between content and ad (within the same content playback) or encounters interruptions.<br />
## <code>'''end'''</code> – SDK instance exits from Processing state when this API is called.<br />
# '''Disabled state''' – The SDK instance is disabled and is not processing playing information. SDK instance moves into this state in one of the following scenarios.<br />
## Initialization fails<br />
## <code>'''appDisableApi'''</code> is set to <code>true</code><br />
<br />
<blockquote>'''Note:''' For API Version 5.1 and above, App SDK will fire data pings and continue measurement even after the user has opted out from Nielsen measurement on a device. The data ping will be marked as opted-out ping.<br />
<br />
'''Note''': In case of any interruptions during playback due to alarm, calendar, call, flight mode, Wi-Fi toggle, channel change, etc., call [[stop]] to stop the measurement.<br />
* As soon as the playback resumes, call <code>'''play'''</code>, <code>'''loadMetadata''' </code> and <code>'''playheadPosition'''</code> </blockquote><br />
<br />
=== API Call Sequence ===<br />
==== Use Case 1: Content has no Advertisements ====<br />
Call [[play()]] with channelName JSON as below.<br />
<syntaxhighlight lang="json">{<br />
"channelName": "TheMovieTitle"<br />
}</syntaxhighlight><br />
Call [[loadMetadata()]] with JSON metadata for content as below.<br />
<syntaxhighlight lang="json">{<br />
"type": "content",<br />
"assetid": "vid345-67483",<br />
"program": "ProgramName",<br />
"title": "Program S3, EP1",<br />
"length": "3600",<br />
...<br />
}</syntaxhighlight><br />
Call [[setPlayheadPosition()]] every one second until a pause / stop.<br />
Use the sample API sequence below as a reference to identify the specific events that need to be called during content playback without ads.<br />
{| class="wikitable"<br />
|-<br />
! Type !! Sample code !! Description<br />
|-<br />
| rowspan="2" | Start of stream || <code>mAppSdk.play(channelName); </code> || // channelName contains JSON metadata of channel/video name being played<br />
|-<br />
| <code>mAppSdk.loadMetadata(contentMetaDataObject);</code> || // contentMetadataObject contains the JSON metadata for the content being played<br />
|-<br />
| Content || <code>mAppSdk.setPlayheadPosition(playheadPosition);</code> || // position is position of the playhead while the content is being played<br />
|-<br />
| End of Stream || <code>mAppSdk.end();</code> || // Content playback is completed.<br />
|}<br />
<br />
==== Use Case 2: Content has Advertisements ====<br />
Call [[play()]] with channelName JSON as below.<br />
<syntaxhighlight lang="json">{<br />
"channelName": "TheMovieTitle"<br />
}</syntaxhighlight><br />
Call [[loadMetadata()]] with JSON metadata for ad as below.<br />
<syntaxhighlight lang="json">{<br />
"type": "preroll",<br />
"assetid": "ad=123"<br />
}</syntaxhighlight><br />
<blockquote>Note: In case the individual ad details are not available, send ad pod (presence) details through the [[loadMetadata]] and playhead position through [[playheadPosition]].</blockquote><br />
<br />
Call [[setPlayheadPosition()]] every one second until a pause / stop / another [[loadMetadata()]] is called. Playhead should be passed for the entire duration of ad pod, if the ad pod details are passed as part of [[loadMetadata()]].<br />
<br />
'''Ad Content'''<br />
<syntaxhighlight lang="java"> long pos = mAdPlayer.videoPosition() / 1000;<br />
if (mAppSdk != null)<br />
{<br />
mAppSdk.setPlayheadPosition(pos);<br />
}</syntaxhighlight><br />
<blockquote>Note: The playhead positions for ad and content should be maintained separately.</blockquote><br />
<br />
The sample API sequence can be used as a reference to identify the specific events that need to be called during content and ad playback.<br />
{| class="wikitable"<br />
|-<br />
! Type !! Sample code !! Description<br />
|-<br />
| rowspan="2" | Start of stream || <code>mAppSdk.play(channelName); </code> || // channelName contains JSON metadata of channel/video name being played<br />
|-<br />
| <code>mAppSdk.loadMetadata(contentMetaDataObject);</code> || // contentMetadataObject contains the JSON metadata for the content being played<br />
|-<br />
| rowspan="3" | Preroll || <code>mAppSdk.loadMetadata(prerollMetadataObject);</code> || // prerollMetadataObject contains the JSON metadata for the preroll ad<br />
|-<br />
| <code>mAppSdk.setPlayheadPosition(playheadPosition);</code> || // position is position of the playhead while the preroll ad is being played<br />
|-<br />
| <code>mAppSdk.stop();</code> || // Call stop after preroll occurs<br />
|-<br />
| rowspan="3" | Content || <code>mAppSdk.loadMetadata(contentMetaDataObject);</code> || // contentMetadataObject contains the JSON metadata for the content being played<br />
|-<br />
| <code>mAppSdk.setPlayheadPosition(playheadPosition);</code> || // position is position of the playhead while the content is being played<br />
|-<br />
| <code>mAppSdk.stop();</code> || // Call stop after the content is paused (ad starts)<br />
|-<br />
| rowspan="6" | Midroll || <code>mAppSdk.loadMetadata(midrollMetaDataObject);</code> || // midrollMetadataObject contains the JSON metadata for the midroll ad<br />
|-<br />
| <code>mAppSdk.setPlayheadPosition(playheadPosition);</code> || // position is position of the playhead while the midroll ad is being played<br />
|-<br />
| <code>mAppSdk.stop();</code> || // App moves to background(midroll pauses) <br />
|-<br />
| <code>mAppSdk.loadMetadata(midrollMetaDataObject);</code> || // App moves to foreground (midroll resumes) <br />
|-<br />
| <code>mAppSdk.setPlayheadPosition(playheadPosition);</code> || // playheadPosition is position of the playhead while the midroll ad is being played <br />
|-<br />
| <code>mAppSdk.stop();</code> || // Call stop after midroll occurs<br />
|-<br />
| rowspan="3" | Content (End of stream) || <code>mAppSdk.loadMetadata(contentMetaDataObject);</code> || // contentMetadataObject contains the JSON metadata for the content being played<br />
|-<br />
| <code>mAppSdk.setPlayheadPosition(playheadPosition);</code> || // position is position of the playhead while the content is being played<br />
|-<br />
| <code>mAppSdk.stop();</code> || // Always call stop irrespective of postroll is followed or not<br />
|-<br />
| End of Stream || <code>mAppSdk.end();</code> || // Call end() at the end of content<br />
|-<br />
| rowspan="3" | Postroll || <code>mAppSdk.loadMetadata(postrollMetaDataObject);</code> || // postrollMetadataObject contains the JSON metadata for the postroll ad<br />
|-<br />
| <code>mAppSdk.setPlayheadPosition(playheadPosition);</code> || // position is position of the playhead while the postroll ad is being played<br />
|-<br />
| <code>mAppSdk.stop();</code> || // Call stop after postroll occurs<br />
|}<br />
<br />
<blockquote>Note: Each Ad playhead should reset or begin from 0 at ad start. When content has resumed following an ad break, playhead position must continue from where previous content segment was left off.</blockquote><br />
<br />
== Handling Foreground and Background states ==<br />
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<br />
* Enable the Nielsen SDK to measure background/foreground state by makingthe relevant update to the AndroidManifest.<br />
* Integrate Nielsen’s SdkBgFgDetectionUtility class within your Custom Application Class.<br />
* Custom implementation of the required methods within your application.<br />
<br />
=== ForeGround/Background Measurement via AndroidManifest ===<br />
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.<br />
<syntaxhighlight lang="java"><br />
<application android:name="com.nielsen.app.sdk.AppSdkApplication"><br />
</syntaxhighlight><br />
<br />
=== Using the Android SdkBgFbDetectionUtility Class ===<br />
For developers who are already using the application class, it is recommended that background/foreground state is implemented using the [https://engineeringportal.nielsen.com/docs/Android_Background_Foreground SdkBgFgDetectionUtility class]. The [https://engineeringportal.nielsen.com/docs/Android_Background_Foreground SdkBgFgDetectionUtility class] is compatible with Android 4+ and has been made available to Nielsen clients. (You will need to copy/paste the code provided into a file).<br />
<br />
=== Manual Background/ForeGround State Management ===<br />
In cases where the developer is not able to use the AndroidManifest.xml solution nor the Nielsen provided [https://engineeringportal.nielsen.com/docs/Android_Background_Foreground 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.<br />
<br />
The SDK is informed about app state using the below methods.<br />
<syntaxhighlight lang="java"><br />
AppLaunchMeasurementManager.appInForeground(getApplicationContext());<br />
AppLaunchMeasurementManager.appInBackground(getApplicationContext());<br />
</syntaxhighlight><br />
Within the lifecycle of individual activities, onResume() and onPause() are best suited to providing indication of the app state.<br />
<br />
<br />
Correct measurement of the foreground/background state is crucial to Static App measurement within Nielsen Digital Content Ratings (DCR).<br />
<br />
== Interruptions during playback ==<br />
As part of integrating Nielsen App SDK with the player application, the Audio / Video app developer needs to handle the following possible interruption scenarios:<br />
* Pause / Play<br />
* Network Loss (Wi-Fi / Airplane / Cellular)<br />
* Call Interrupt (SIM or Third party Skype / Hangout call)<br />
* Alarm Interrupt<br />
* Content Buffering<br />
* Device Lock / Unlock (Video players only, not for Audio players)<br />
* App going in the Background/Foreground (Video players only, not for Audio players)<br />
* Channel / Station Change Scenario<br />
* Unplugging of headphone<br />
In case of encountering one of the above interruptions, the player application needs to<br />
* Call [[stop]] immediately (except when content is buffering) and withhold sending playhead position.<br />
* Start sending pings – <code>'loadMetadata'</code> and <code>'playheadPosition'</code> for the new viewing session, once the playback resumes.<br />
Please see the [https://engineeringportal.nielsen.com/docs/Digital_Measurement_Interruption_Scenarios Interruption Scenarios Page] for more details<br />
<br />
== Pre-Certification Checklists ==<br />
After the application is ready to be sent for Nielsen Certification, please go through the [[Digital Pre-Certification Checklist App SDK]] and ensure the app behaves as expected, before submitting to Nielsen.<br />
<br />
{{Template:Android_Privacy_and_Opt-Out}}<br />
<br />
== Going Live ==<br />
Following Nielsen testing, users need to make one update to the initialization call to ensure that the site is being measured properly.<br />
<br />
# '''Debug Logging''': Disable logging by deleting <code>{nol_sdkDebug: 'DEBUG'}</code> from initialization call.<br />
'''Note''': before going live you have to inform Nielsen team - this is necessary, because Nielsen team has to adjust internal configuration parameter to enable data collection. Without that notification no data will be collected and no data will be reported.<br />
<br />
== Sample Applications ==<br />
The below sample applications have been designed to show the API's functionality and are broken into two distinct categories:<br />
* '''Basic''' - To show the functionality of the Nielsen API using a standard no-frills player.<br />
** [[Swift Basic Sample|Swift 4.0 Sample]]<br />
** [[Objective-c Basic example|Objective-C Sample]]<br />
** [[Android Basic example|Android Studio Example]]<br />
<br />
* '''Advanced''' - Nielsen API integrated into a custom video player is bundled with the SDK.</div>AlexGutierrezhttps://engineeringportal.nielsen.com//w/index.php?title=Digital_Measurement_Browser_Simplified_API&diff=3527Digital Measurement Browser Simplified API2019-03-12T14:31:44Z<p>AlexGutierrez: Removed Digital Audio mention (product being retired)</p>
<hr />
<div>{{Breadcrumb|}} {{Breadcrumb|Digital}} {{Breadcrumb|DCR & DTVR}} {{CurrentBreadcrumb}}<br />
[[Category:Digital]]<br />
== Overview ==<br />
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.<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]]), and [[Digital Ad Ratings]] (DAR). Nielsen SDKs are also equipped to measure static content and can track key life cycle events of an application like:<br />
*Application launch events and how long app was running<br />
*Time of viewing a sub section / page in the application.<br />
== Prerequisites ==<br />
To start using the Simplified SDK for Browser, you will need an '''Appid.''' This is a Unique ID assigned to the player/site and configured by product.<br />
If you do not have an AppID , please contact our SDK sales support team.<br />
Refer to [[Digital Measurement Onboarding]] guide for information on how to get a Nielsen App SDK and appid.<br />
<br />
== Simplified SDK API ==<br />
As part of making the SDK more user friendly and reduce the number of app integration touch points, Nielsen has designed a simple interface to pass metadata to the sdk while reducing the number of API calls. The new <code> trackevent() </code> API has been implemented as a wrapper for the existing SDK and will be responsible for handling new API calls, performing validation, and translation of 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.<br />
[[File:SimplifiedAPI_vs_StandardAPI_New.jpg|2048px|link=http://engineeringportal.nielsen.com/w/images/9/91/SimplifiedAPI_vs_StandardAPI_New.jpg]]<br />
[http://engineeringportal.nielsen.com/w/images/9/91/SimplifiedAPI_vs_StandardAPI_New.jpg Click here to zoom in on image]<br />
<br />
== Manages the order of metadata (ad vs content and playheads) ==<br />
Existing API has a number of methods used for reporting player and application state changes to the SDK. Order of calls is important for the SDK in the existing API. In the new enhanced 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 existing API in separate calls will be provided in one single call. SDK will analyse the data received in the dictionary object, compare it with the data received previously and generate a sequence of calls for the existing API.<br />
== Everything passed via trackevent() ==<br />
The New API method supports the following event types:<br />
{| class="wikitable"<br />
!Key<br />
!Description<br />
|-<br />
|'''playhead'''||<br />
It is used to pass content, ad or static metadata, the current playhead value, UNIX timestamp or id3 payload, ott information to the SDK.<br />
|-<br />
|'''pause'''||<br />
This event should be passed when the<br />
content playback is paused. <br />
|-<br />
|'''complete'''||<br />
It is called when session is completed or ends.<br />
|-<br />
|'''adStop'''||<br />
Should be called at the end of each ad. This event type is required to handle the case when advertisements could not be distinguished, as its assetId is the same.<br />
|}<br />
<br />
__TOC__<br />
<br />
== Prerequisites ==<br />
To get started, an App ID is needed. The App ID is a unique ID assigned to the player/site/app. This will be provided upon starting the integration.<br />
<syntaxhighlight lang="javascript">'PXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'</syntaxhighlight><br />
<br />
== Configure SDK ==<br />
There are two steps required for configuring the SDK:<br />
* Add Static Queue Snippet<br />
* Create SDK Instance<br />
=== Add Static Queue Snippet ===<br />
Add the following script tag to the website:<br />
<syntaxhighlight lang="javascript"><br />
<script type = "text/javascript" ><br />
/***************** Static Queue Snippet *********************/<br />
! function(t, n) {<br />
t[n] = t[n] || {<br />
nlsQ: function(e, c, o, r, s, i) {<br />
return s = t.document,<br />
r = s.createElement("script"),<br />
r.async = 1,<br />
r.src = ("http:" === t.location.protocol ? "http:" : "https:") + "//cdn-gl.imrworldwide.com/conf/" + e + ".js#name=" + c + "&ns=" + n, i = s.getElementsByTagName("script")[0], i.parentNode.insertBefore(r, i),<br />
t[n][c] = t[n][c] || {<br />
g: o || {},<br />
ggPM: function(e, o, r, s, i) {<br />
(t[n][c].q = t[n][c].q || []).push([e, o, r, s, i])<br />
},<br />
trackEvent: function(e) {<br />
(t[n][c].te = t[n][c].te || []).push(e)<br />
}<br />
}, t[n][c]<br />
}<br />
}<br />
}(window, "NOLBUNDLE"); <br />
</script><br />
</syntaxhighlight><br />
<br />
The static queue snippet allows the SDK APIs to be called while the actual SDK and configuration file are still being downloaded. As the queue can capture all API calls before the download completes, there is no wait time. Once the SDK is available, the API calls will transition from directing to the queue to the SDK seamlessly.<br />
<br />
===Create SDK Instance===<br />
To initialize the SDK, create an SDK instance by making the initialization call:<br />
<br />
==== Initialization API Call ====<br />
<syntaxhighlight lang="javascript"><br />
NOLBUNDLE.nlsQ("<apid>", "<instanceName>",{nol_sdkDebug: "debug", optout: "false"})<br />
</syntaxhighlight><br />
<br><br />
When creating an instance, pass the following values: (<code>nol_sdkDebug</code> and <code>optout</code> are optional)<br />
{| class="wikitable"<br />
|-<br />
! Parameter !! Description !! Values<br />
|-<br />
| apid || Unique ID assigned to player/site || 'PXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'<br />
|-<br />
|instanceName || Name of SDK instance || "any string value"<br />
|-<br />
| nol_sdkDebug || Enables Nielsen console logging. Only required for testing || "{nol_sdkDebug: "debug"})"<br />
|-<br />
|optout || OptOut global parameter (Optional Parameter) || <code>1/0</code> or <code>true/false</code><br />
|}<br />
<br />
==== Example SDK Initialization ====<br />
When the initialization call is made, a unique static configuration file, <apid>.js, will be downloaded based on the apid and will be cached on the user’s browser.<br />
<br />
Once the configuration is downloaded, the SDK itself will be downloaded and initialized. All SDK modules are included in one file: “nlsSDK600.bundle.min.js”.<br />
<br />
More information on OptOut Parameter under [[DCR Video Browser Bundled SDK#Privacy_and_Opt-Out|Privacy and Opt-Out.]]<br />
<br />
=== Example SDK Configuration ===<br />
<br />
The configuration should include the Static Queue Snippet and an SDK Instance for an unique App ID as shown in the example:<br />
<syntaxhighlight lang="javascript"><br />
<script type = "text/javascript" ><br />
/***************** Static Queue Snippet *********************/<br />
! function(t, n) {<br />
t[n] = t[n] || {<br />
nlsQ: function(e, c, o, r, s, i) {<br />
return s = t.document,<br />
r = s.createElement("script"),<br />
r.async = 1,<br />
r.src = ("http:" === t.location.protocol ? "http:" : "https:") + "//cdn-gl.imrworldwide.com/conf/" + e + ".js#name=" + c + "&ns=" + n, i = s.getElementsByTagName("script")[0], i.parentNode.insertBefore(r, i),<br />
t[n][c] = t[n][c] || {<br />
g: o || {},<br />
ggPM: function(e, o, r, s, i) {<br />
(t[n][c].q = t[n][c].q || []).push([e, o, r, s, i])<br />
},<br />
trackEvent: function(e) {<br />
(t[n][c].te = t[n][c].te || []).push(e)<br />
}<br />
}, t[n][c]<br />
}<br />
}<br />
}(window, "NOLBUNDLE"); <br />
<br />
// Created SDK Instance<br />
var nSdkInstance = NOLBUNDLE.nlsQ("XXXXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXX", "nlsnInstance", {<br />
nol_sdkDebug: "debug",<br />
outout: "false"<br />
});<br />
</script></syntaxhighlight><br />
<br />
== Simplified API Syntax ==<br />
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. <br />
<br />
Main API call for the new NielsenEventTracker API:<br />
<br/><br />
<syntaxhighlight lang="java"> nSdkInstance.trackEvent({metadata})<br />
</syntaxhighlight><br />
<br />
=== Handling JSON Metadata ===<br />
Parameter “data” is a JSON object with many key-value pairs that holds all information required by SDK.<br />
<br />
Format of input object is the following:<br />
<syntaxhighlight lang="json"><br />
{ <br />
"event": <event identifier>,<br />
"type": <type of metadata>,<br />
"metadata":{ <br />
"content": <content metadata object>,<br />
"ad": <ad metadata object>,<br />
"static": <static metadata object><br />
},<br />
"playheadPosition":<playhead value | UNIX timestamp (seconds since Jan-1-1970 UTC) >,<br />
"id3Data": <id3 payload>,<br />
}<br />
</syntaxhighlight><br />
<br><br />
=== Event Types ===<br />
The New API method supports the following event types:<br />
{| class="wikitable"<br />
!Key<br />
!Description<br />
|-<br />
|'''playhead'''||<br />
It is used to pass content, ad or static metadata, the current playhead value, Unix timestamp or id3 payload, ott information to the SDK.<br />
|-<br />
|'''pause'''||<br />
This event should be used when<br />
content playback is paused. <br > (Pause is detected by SDK automatically when the time gap between commands is more than 30 minutes.)<br />
|-<br />
|'''complete'''||<br />
It is called when session is completed or ends.<br />
|-<br />
|'''adStop'''||<br />
Should be called at the end of each ad. This event type is required to handle the case when advertisements could not be distinguished, as its assetId is the same.<br />
|}<br />
<br><br />
DCR and DTVR require various levels of data. Please select the TAB of the product you are interested in reviewing.<br />
{{DCRDTVRTabs<br />
|DCR=<br />
=== Digital Content Ratings===<br />
<table><br />
<tr><br />
<th> Parameter<br />
</th><br />
<th> <b>Description</b><br />
</th><br />
<th> <b>Supported values</b><br />
</th><br />
<th> <b>Example</b><br />
</th></tr><br />
<tr><br />
<td> <b>event</b><br />
</td><br />
<td> Event identifier<br />
</td><br />
<td><br />
<p><code> String:<br />
playhead,pause,complete,<br />
adStop</code><br />
</p><br />
</td><br />
<td><syntaxhighlight lang="swift">"event":"playhead"</syntaxhighlight><br />
</td></tr><br />
<tr><br />
<td> <b>type</b><br />
</td><br />
<td> Determines the metadata object <br />
that should be used for crediting.<br />
</td><br />
<td><br />
<p><code> String:<br /><br />
content,<br />
ad,<br />
static</code><br />
</p><br />
</td><br />
<td><syntaxhighlight lang="swift">"type":"content"</syntaxhighlight><br />
</td></tr><br />
<tr><br />
<td> <b>metadata</b><br />
</td><br />
<td> Object that holds metadata values of specific types<br /><br />
<p><span style="color:blue"> Detailed in tables below</span><br />
</p><br />
</td><br />
<td> <code>Object</code><br />
</td><br />
<td><syntaxhighlight lang="swift"><br />
"metadata":{ <br />
"content": <content metadata object>,<br />
"ad": <ad metadata object>,<br />
"static": <static metadata object><br />
},<br />
</syntaxhighlight><br />
</td></tr><br />
<tr><br />
<td> <b>playheadPosition</b><br />
</td><br />
<td> Playhead value in seconds or Unix timestamp<br />
</td><br />
<td> <code>String</code><br />
</td><br />
<td><br />
<p>Position value is Unix timestamp:<br />
<code><br />
"playheadPosition":"1543437655"</code><br />
</p><p>Position value is playhead:<br />
<code><br />
"playheadPosition":"10"</code><br />
</p><br />
</td></tr><br />
</table><br />
=== Content Metadata ===<br />
Content metadata sent for every playheadposition update.<br />
<table><br />
<tr><br />
<th> Keys </th><br />
<th> Description </th><br />
<th> Example </th><br />
<th> Required<br />
</th></tr><br />
<tr><br />
<td> '''assetid''' </td><br />
<td> unique ID assigned to asset </td><br />
<td> custom </td><br />
<td> Yes<br />
</td></tr><br />
<tr><br />
<td> '''length''' </td><br />
<td> length of content in seconds </td><br />
<td> <code>seconds</code> (86400 for live stream) </td><br />
<td> Yes<br />
</td></tr><br />
<tr><br />
<td> '''program''' </td><br />
<td>name of program (100 character limit) </td><br />
<td> custom </td><br />
<td> Yes<br />
</td></tr><br />
<tr><br />
<td> '''title''' </td><br />
<td>name of program (100 character limit) </td><br />
<td> custom </td><br />
<td> Yes<br />
</td></tr><br />
<tr><br />
<td>'''type'''</td><br />
<td><code>'content', 'ad', 'static'</code></td><br />
<td> <code> 'content'</code> </td><br />
<td> Yes<br />
</td></tr><br />
<tr><br />
<td> '''airdate''' </td><br />
<td> the airdate in the linear TV ++</td><br />
<td> YYYYMMDD HH24:MI:SS </td><br />
<td> Yes<br />
</td></tr><br />
<tr><br />
<td> '''isfullepisode''' </td><br />
<td> full episode flag </td><br />
<td> <code>"y"</code>- full episode, <code>"n"</code>- non full episode </td><br />
<td> Yes<br />
</td></tr><br />
<tr><br />
<td> '''crossId1''' </td><br />
<td> Gracenote ID or Enterprise ID </td><br />
<td> custom </td><br />
<td> Yes<br />
</td></tr><br />
<tr><br />
<td> '''crossId2 '''</td><br />
<td> content originator or network (only required for distributors) </td><br />
<td> custom </td><br />
<td><br />
</td></tr><br />
<tr><br />
<td> <b>adloadtype</b><br />
</td><br />
<td> linear vs dynamic ad model<br />
</td><br />
<td> 1=Linear<br />
2=Dynamic Ads<br />
</td><br />
<td>custom<br />
</td></tr><br />
</table><br />
++ Acceptable '''Air Date''' Formats:<br />
<syntaxhighlight lang="json"><br />
YYYYMMDD HH24:MI:SS<br />
YYYYMMDDHH24MISS<br />
YYYY-MM-DDTHH:MI:SS<br />
YYYY-MM-DDHH:MI:SS <br />
YYYYMMDDHH:MI:SS<br />
MM-DD-YYYY<br />
YYYYMMDD HH:MI:SS<br />
</syntaxhighlight><br />
<br><br />
For USA all times should be EST, for all other countries Local Time.<br />
Below is a sample event for DCR. If no ad or static values, these can be left as blank/null.<br />
<syntaxhighlight lang="json"><br />
{ <br />
"event": "playhead",<br />
"type": "content",<br />
"metadata": { <br />
"content":{<br />
"assetid":"B66473",<br />
"length":"3600",<br />
"program":"MyProgram",<br />
"title":"S2,E3",<br />
"type":"content",<br />
"airdate":"20180120 10:00:00",<br />
"isfullepisode":"y",<br />
"crossId1":"Standard Episode ID",<br />
"crossId2" :"Content Originator",<br />
"adloadtype":"2"},<br />
"ad": {},<br />
"static": {}<br />
},<br />
"playheadPosition": "",<br />
}<br />
</syntaxhighlight><br />
<br />
|DTVR=<br />
=== Digital TV Ratings info ===<br />
<table><br />
<tr><br />
<th> Parameter<br />
</th><br />
<th> <b>Description</b><br />
</th><br />
<th> <b>Supported values</b><br />
</th><br />
<th> <b>Example</b><br />
</th></tr><br />
<tr><br />
<td> <b>event</b><br />
</td><br />
<td> Event identifier<br />
</td><br />
<td><br />
<p><code> String:<br />
playhead,pause,complete,<br />
adStop</code><br />
</p><br />
</td><br />
<td><syntaxhighlight lang="swift">"event":"playhead"</syntaxhighlight><br />
</td></tr><br />
<tr><br />
<td> <b>type</b><br />
</td><br />
<td> Determines the metadata object that should be used for crediting.<br />
</td><br />
<td><br />
<p><code> String:<br /><br />
content,<br />
ad,<br />
static</code><br />
</p><br />
</td><br />
<td><syntaxhighlight lang="swift">"type":"content"</syntaxhighlight><br />
</td></tr><br />
<tr><br />
<td> <b>adModel</b><br />
</td><br />
<td> linear vs dynamic ad model<br />
</td><br />
<td> 1=Linear<br />
2=Dynamic Ads<br />
</td><br />
<td>custom<br />
</td></tr><br />
<tr><br />
<td> <b>channelName</b><br />
</td><br />
<td> Any string representing the.channel/stream<br />
</td><br />
<td> <code>Object</code><br />
</td><br />
<td>custom<br />
</td></tr><br />
<tr><br />
<td> <b>playheadPosition</b><br />
</td><br />
<td> Playhead value in seconds or Unix timestamp<br />
</td><br />
<td> <code>String</code><br />
</td><br />
<td><br />
<p>Position value is Unix timestamp:<br />
<code><br />
"playheadPosition":"1543437655"</code><br />
</p><p>Position value is playhead:<br />
<code><br />
"playheadPosition":"10"</code><br />
</p><br />
</td></tr><br />
<tr><br />
<td> <b>id3Data</b><br />
</td><br />
<td> Nielsen ID3 payload string<br />
</td><br />
<td> <code>Object</code><br />
</td><br />
<td><br />
<p><code><br />
id3Data: www.nielsen.com/065H2g6E7ZyQ5UdmMAbbpg<br />
==/_EMc37zfVgq_8KB7baUYfg==/ADQCAmgV1Xyvnynyg60<br />
kZO_Ejkcn2KLSrTzyJpZZ-QeRn8GpMGTWI7-HrEKzghxyzC<br />
yBEoIDv2kA2g1QJmeYOl5GnwfrLDVK2bNLTbQxr1z9VBfxa<br />
hBcQP5tqbjhyMzdVqrMKuvvJO1jhtSXa9AroChb11ZUnG1W<br />
VJx2O4M=/33648/22847/00</code><br />
</p><br />
<tr><br />
<br />
</td></tr><br />
</table><br />
Below is a sample event for DTVR. If no ad or static values, these can be left as blank/null.<br />
<syntaxhighlight lang="json"><br />
{ <br />
"event": "playhead",<br />
"type": "content",<br />
"metadata": { <br />
"content":{<br />
"adModel":"1",<br />
"channelname":"channel1"<br />
},<br />
"ad": {},<br />
"static": {}<br />
},<br />
"playheadPosition": "",<br />
"id3Data": "www.nielsen.com/065H2g6E7ZyQ5UdmMAbbpg==/_<br />
EMc37zfVgq_8KB7baUYfg==/ADQCAmgV1Xyvnynyg60kZO_Ejkcn<br />
2KLSrTzyJpZZ-QeRn8GpMGTWI7-HrEKzghxyzCyBEoIDv2kA2g1Q<br />
JmeYOl5GnwfrLDVK2bNLTbQxr1z9VBfxahBcQP5tqbjhyMzdVqrMK<br />
uvvJO1jhtSXa9AroChb11ZUnG1WVJx2O4M=/33648/22847/00"<br />
}<br />
</syntaxhighlight><br />
|DCRDTVR=<br />
=== Applies to DCR and DTVR ===<br />
<table><br />
<tr><br />
<th> Parameter<br />
</th><br />
<th> <b>Description</b><br />
</th><br />
<th> <b>Supported values</b><br />
</th><br />
<th> <b>Example</b><br />
</th></tr><br />
<tr><br />
<td> <b>event</b><br />
</td><br />
<td> Event identifier<br />
</td><br />
<td><br />
<p><code> String:<br />
playhead,<br />
pause,<br />
complete,<br />
adStop</code><br />
</p><br />
</td><br />
<td><syntaxhighlight lang="swift">"event":"playhead"</syntaxhighlight><br />
</td></tr><br />
<tr><br />
<td> <b>type</b><br />
</td><br />
<td> Determines the metadata object that should be used for crediting.<br />
</td><br />
<td><br />
<p><code> String:<br /><br />
content,<br />
ad,<br />
static</code><br />
</p><br />
</td><br />
<td><syntaxhighlight lang="swift">"type":"content"</syntaxhighlight><br />
</td></tr><br />
<tr><br />
<td> <b>metadata</b><br />
</td><br />
<td> Object that holds metadata values of specific types<br /><br />
<p><span style="color:blue"> Detailed in tables below</span><br />
</p><br />
</td><br />
<td> <code>Object</code><br />
</td><br />
<td><syntaxhighlight lang="swift"><br />
"metadata":{ <br />
"content": <content metadata object>,<br />
"ad": <ad metadata object>,<br />
"static": <static metadata object><br />
},<br />
</syntaxhighlight><br />
</td></tr><br />
<tr><br />
<td> <b>playheadPosition</b><br />
</td><br />
<td> Playhead value in seconds or Unix timestamp<br />
</td><br />
<td> <code>String</code><br />
</td><br />
<td><br />
<p>Position value is Unix timestamp:<br />
<code><br />
"playheadPosition":"1543437655"</code><br />
</p><p>Position value is playhead:<br />
<code><br />
"playheadPosition":"10"</code><br />
</p><br />
</td></tr><br />
<tr><br />
<td> <b>id3Data</b><br />
</td><br />
<td> Nielsen ID3 payload string<br />
</td><br />
<td> <code>Object</code><br />
</td><br />
<td><br />
<p><code><br />
id3Data: www.nielsen.com/065H2g6E7ZyQ5UdmMAbbpg<br />
==/_EMc37zfVgq_8KB7baUYfg==/ADQCAmgV1Xyvnynyg60<br />
kZO_Ejkcn2KLSrTzyJpZZ-QeRn8GpMGTWI7-HrEKzghxyzC<br />
yBEoIDv2kA2g1QJmeYOl5GnwfrLDVK2bNLTbQxr1z9VBfxa<br />
hBcQP5tqbjhyMzdVqrMKuvvJO1jhtSXa9AroChb11ZUnG1W<br />
VJx2O4M=/33648/22847/00</code><br />
</p><br />
</td></tr><br />
<tr><br />
<td> <b>ottData</b><br />
</td><br />
<td> Object that holds ott information<br />
</td><br />
<td> <code>Object</code><br />
</td><br />
<td><syntaxhighlight lang="swift"><br />
ottData:{<br />
ottStatus:1,<br />
ottType:casting,<br />
ottDevice:chromecast,<br />
ottDeviceName:Google ChromeCast,<br />
ottDeviceID:1234,<br />
ottDeviceModel:ChromeCast,<br />
ottDeviceVersion:1.0.0<br />
}<br />
</syntaxhighlight><br />
</td></tr><br />
</table><br />
=== Content Metadata ===<br />
Content metadata sent for every playheadposition update.<br />
<table><br />
<tr><br />
<th> Keys </th><br />
<th> Description </th><br />
<th> Example </th><br />
<th> Required<br />
</th></tr><br />
<tr><br />
<td> '''assetid''' </td><br />
<td> unique asset ID for DCR; can be set to "0" for DTVR </td><br />
<td> <code>EP732481</code> </td><br />
<td> Yes<br />
</td></tr><br />
<tr><br />
<td> '''length''' </td><br />
<td> length of content in seconds </td><br />
<td> <code>seconds</code> (86400 for live stream) </td><br />
<td> Yes<br />
</td></tr><br />
<tr><br />
<td>'''type'''</td><br />
<td><code>'content', 'ad', 'static'</code></td><br />
<td> <code> 'content'</code> </td><br />
<td> Yes<br />
</td></tr><br />
<tr><br />
<td> <b>adModel</b><br />
</td><br />
<td> linear vs dynamic ad model<br />
</td><br />
<td> 1=Linear<br />
2=Dynamic Ads<br />
</td><br />
<td>custom<br />
</td></tr><br />
<tr><br />
<td> <b>adloadtype</b><br />
</td><br />
<td> DCR Ad Model<br />
</td><br />
<td> 1=Linear<br />
2=Dynamic Ads<br />
</td><br />
<td>custom<br />
</td></tr><br />
</table><br />
++ Acceptable '''Air Date''' Formats:<br />
<syntaxhighlight lang="json"><br />
YYYYMMDD HH24:MI:SS<br />
YYYYMMDDHH24MISS<br />
YYYY-MM-DDTHH:MI:SS<br />
YYYY-MM-DDHH:MI:SS <br />
YYYYMMDDHH:MI:SS<br />
MM-DD-YYYY<br />
YYYYMMDD HH:MI:SS<br />
</syntaxhighlight><br />
<br><br />
Below is a sample event for DCR/DTVR joint integration. If no ad or static values, these can be left as blank/null.<br />
<syntaxhighlight lang="json"><br />
{ <br />
"event": "playhead",<br />
"type": "content",<br />
"metadata": { <br />
"content":{<br />
"type":"content",<br />
"assetid":"0",<br />
"length":"86400",<br />
"adModel":"1",<br />
"adloadtype":"1"},<br />
"ad": {},<br />
"static": {}<br />
},<br />
"playheadPosition": "",<br />
"id3Data": "www.nielsen.com/065H2g6E7ZyQ5UdmMAbbpg==/_<br />
EMc37zfVgq_8KB7baUYfg==/ADQCAmgV1Xyvnynyg60kZO_Ejkcn<br />
2KLSrTzyJpZZ-QeRn8GpMGTWI7-HrEKzghxyzCyBEoIDv2kA2g1Q<br />
JmeYOl5GnwfrLDVK2bNLTbQxr1z9VBfxahBcQP5tqbjhyMzdVqrMK<br />
uvvJO1jhtSXa9AroChb11ZUnG1WVJx2O4M=/33648/22847/00"<br />
}<br />
</syntaxhighlight><br />
}}<br />
<br />
=== Ad Metadata ===<br />
The ad metadata (if applicable) should be passed for each individual ad, if ads are available during or before the stream begins.<br />
<br/><br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Example<br />
|-<br />
| assetid || unique ID assigned to ad || custom <br>(no [[Special Characters]]) || <code>'AD1234'</code><br />
|-<br />
| title || unique name assigned to ad || custom ||<code>'ADtitle'</code><br />
|-<br />
|adldx || Ad Index (*See Note below*) || custom || <code> "66478364" </code><br />
|-<br />
| type || type of ad || 'preroll', 'midroll', or 'postroll' || <code>'preroll'</code><br />
|-<br />
|length || length of ad || In Seconds ||<code> '20' </code><br />
|}<br />
=== Ad Metadata Sample ===<br />
<syntaxhighlight lang="json"><br />
{<br />
"ad": {<br />
"assetid":"AD12345",<br />
"title":"ADTestTitle",<br />
"adldx":"1",<br />
"type":"preroll",<br />
"length":"20"<br />
},<br />
}<br />
</syntaxhighlight><br />
=== Managing Ads ===<br />
If there is an Ad block within the playing content (such as a midroll) you need to:<br />
* Reset the playhead position to 0 for each ad.<br />
* Call the '''adStop''' event at the end of each ad or increment the adldx<br />
<br />
The Simplified SDK will can automatically detect the change from ad to content, or even ad to ad if the assetID changes; however, there could be situations where the same ad is played back to back. You can either increment/change the '''adldx value''', and/or call adStop at the end of each Ad.<br />
<br />
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 Nielsen Simplified API will support a new parameter for ad metadata: '''adIdx.''' This parameter is just 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.<br />
<br />
<syntaxhighlight lang="swift"><br />
// Example of passing both values<br />
self.data.updateValue("adStop", forKey: "event")<br />
self.data.updateValue("223", forKey: "adldx")<br />
self.nielsenEventTracker.trackEvent(data)<br />
</syntaxhighlight><br />
=== Static Metadata ===<br />
The values passed through the Nielsen keys will determine the breakouts that are seen in reporting. The custom segments (A, B & C) will roll into the sub-brand. To not use custom segments A, B and C, do not pass any value in these keys.<br />
<br/><br />
{| class="wikitable"<br />
|-<br />
! Key !! Description !! Data Type !! Value !! Required?<br />
|-<br />
| assetid || Unique ID for each article || dynamic || custom <br>(no [[Special Characters]]) || Yes<br />
|-<br />
| section || Section of each site which is limited to 25 unique values<br> (e.g. section value should be first level in page URL: website.com/section). || dynamic || custom || Yes<br />
|-<br />
| segA || custom segment for reporting: Limit to 25 unique values across custom segments<br> (segA + segB + segC) || dynamic || custom || No<br />
|-<br />
| segB || custom segment for reporting: Limit to 25 unique values across custom segments<br> (segA + segB + segC) || dynamic || custom || No<br />
|-<br />
| segC || custom segment for reporting: Limit to 25 unique values across custom segments<br> (segA + segB + segC) || dynamic || custom || No<br />
|}<br />
<br />
'''Aggregation Limits'''<br />
There are limits on the number of unique values that can be aggregated on in reporting. The specific limitations by key are:<br />
{| class="wikitable"<br />
|-<br />
! Key !! Aggregation Limit<br />
|-<br />
| section || maximum of 25 unique values (section <= 25)<br />
|-<br />
| segA || Maximum number of unique values allowed across segA, segB, and segC is 25 (segA + segB + segC<= 25)<br />
|-<br />
| segB || Maximum number of unique values allowed across segA, segB, and segC is 25 (segA + segB + segC<= 25)<br />
|-<br />
| segC || Maximum number of unique values allowed across segA, segB, and segC is 25 (segA + segB + segC<= 25)<br />
|}<br />
<br />
=== Static Metadata Sample ===<br />
<syntaxhighlight lang="json"><br />
// Sample input Object for static metadata<br />
// Since only recording static data, playheadposition = ""<br />
{ <br />
"metadata": { <br />
"content": {},<br />
"ad": {},<br />
"static": { <br />
"type":"static",<br />
"assetid":"Static1",<br />
"section":"Page",<br />
"segA":"S07E04:NBC",<br />
"segB":"teams",<br />
"segC":"Atlanta",<br />
"crossId1":"Reference"<br />
}<br />
},<br />
"event": "playhead",<br />
"type": "static",<br />
"playheadPosition": "",<br />
}<br />
</syntaxhighlight><br />
=== Json Examples ===<br />
[https://engineeringportal.nielsen.com/docs/Digital_Measurement_Simplified_SDK_Supplements Addtional JSON input object examples.]<br />
{{Template:Browser_Privacy_and_Opt-Out}}<br />
<br />
== Going Live ==<br />
After the integration has been certified, users will need to make a couple of updates to the initialization call to ensure that player will be measured properly.<br />
Disable debug logging by deleting {nol_sdkDebug: 'DEBUG'} from initialization call.<br />
Example Production Initialization Call<br />
<br />
<syntaxhighlight lang="javascript"><br />
var nSdkInstance = NOLBUNDLE.nlsQ("XXXXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXX", "nlsnInstance", { outout: "false"});<br />
</syntaxhighlight><br />
<br />
== Sample Code ==<br />
This code is for example purposes only to demonstrate a very simple video implementation, with a single preroll and single content block.<br />
<syntaxhighlight lang=javascript><br />
<script ><br />
// Setup videojs sample player<br />
var nielsenid = document.getElementById("my-sdkplayer");<br />
var coolPlayer = videojs('my-sdkplayer', {<br />
controls: true,<br />
autoplay: true,<br />
preload: 'auto',<br />
});<br />
var prevPos = null;<br />
<br />
coolPlayer.preroll({<br />
src: {<br />
src: "assets/CleaningCrew.mp4",<br />
type: "video/mp4"<br />
},<br />
href: "http://videojs.com",<br />
adsOptions: {<br />
debug: true<br />
}<br />
});<br />
<br />
videojs('my-sdkplayer').ready(function() {<br />
// EXAMPLE: Start playing the video.<br />
<br />
coolPlayer.play();<br />
var obj = {<br />
event: 'playhead', // playhead,pause,complete,adstop<br />
type: 'content',<br />
playheadPosition: '0',<br />
metadata: {<br />
content: {<br />
type: 'content',<br />
assetid: 'VID98-2B88',<br />
program: 'Put a programname here ',<br />
title: 'S01E02',<br />
length: '180',<br />
airdate: '20190101 10:01:00',<br />
isfullepisode: 'y',<br />
crossId1: 'Standard Episode ID',<br />
crossID2: 'Content Originator',<br />
adloadtype: '2'<br />
},<br />
ad: {},<br />
static: {}<br />
}<br />
};<br />
<br />
coolPlayer.on('timeupdate', function() {<br />
var getcurrentTime = coolPlayer.currentTime();<br />
var intPlayedTime = parseInt(getcurrentTime, 10);<br />
if (intPlayedTime > 0 && intPlayedTime !== prevPos && !coolPlayer.scrubbing()) {<br />
// check for playhead change so it's only called once per second <br />
console.log('PLAYHEAD EVENT **************************************************************');<br />
console.log(Math.round(getcurrentTime));<br />
obj.event = 'playhead';<br />
obj.type = 'content';<br />
obj.metadata.ad = {};<br />
prevPos = intPlayedTime;<br />
obj.playheadPosition = Math.round(getcurrentTime);<br />
nSdkInstance.trackEvent(obj);<br />
}<br />
});<br />
<br />
this.on('adstart', function() {<br />
console.log('ADSTART EVENT **************************************************************');<br />
obj.type = 'ad';<br />
obj.metadata.ad = {<br />
type: 'preroll',<br />
assetid: 'AD9455',<br />
adldx: '1',<br />
program: 'Cant Skip Campaign',<br />
title: 'Cleaning Crew',<br />
length: '36',<br />
};<br />
nSdkInstance.trackEvent(obj);<br />
});<br />
<br />
this.on('contentresumed', function() {<br />
console.log('CONTENT RESUMED EVENT **************************************************************');<br />
obj.type = 'content';<br />
obj.event = 'playhead';<br />
obj.metadata.ad = {};<br />
nSdkInstance.trackEvent(obj);<br />
});<br />
<br />
this.on('pause', function() {<br />
console.log('PAUSE EVENT **************************************************************');<br />
obj.event = 'pause';<br />
obj.playheadPosition = Math.round(coolPlayer.currentTime());<br />
nSdkInstance.trackEvent(obj);<br />
});<br />
<br />
this.on('adtimeupdate', function() {<br />
var getcurrentTime = coolPlayer.currentTime();<br />
var intPlayedTime = parseInt(getcurrentTime, 10);<br />
if (intPlayedTime > 0 && intPlayedTime !== prevPos) {<br />
// check for playhead change so it's only called once per second <br />
console.log('AD PLAYHEAD EVENT **************************************************************');<br />
console.log(Math.round(getcurrentTime));<br />
prevPos = intPlayedTime;<br />
obj.playheadPosition = Math.round(getcurrentTime);<br />
nSdkInstance.trackEvent(obj);<br />
};<br />
});<br />
<br />
this.on('adend', function() {<br />
console.log('AD STOP EVENT **************************************************************');<br />
obj.event = 'adstop';<br />
nSdkInstance.trackEvent(obj);<br />
});<br />
<br />
this.on('ended', function() {<br />
console.log('COMPLETE EVENT **************************************************************');<br />
obj.event = 'complete';<br />
nSdkInstance.trackEvent(obj);<br />
});<br />
});<br />
<br />
</script><br />
</syntaxhighlight></div>AlexGutierrezhttps://engineeringportal.nielsen.com//w/index.php?title=Digital_Measurement_Android_Artifactory_Guide&diff=3526Digital Measurement Android Artifactory Guide2019-03-08T17:10:21Z<p>AlexGutierrez: Grammar fixes</p>
<hr />
<div>{{Breadcrumb|}} {{Breadcrumb|Digital}} {{Breadcrumb|DCR & DTVR}} {{CurrentBreadcrumb}}<br />
[[Category:Digital]]<br />
<br />
__NOTOC__<br />
The Nielsen AppSDK can either be downloaded directly from the [http://engineeringportal.nielsen.com Engineering Portal], or can be integrated directly within an application through the use of a CocoaPod or Gradle.<br />
<br />
== How to install the Nielsen AppSDK using Gradle for Android ==<br />
Below are the steps which need to be performed by app developers to integrate the Nielsen App SDK within an Android application.<br />
<br />
=== Update grade.properties ===<br />
The first step is to add the credentials received from Nielsen into your '''gradle.properties''' file, typically near the end of the file. We recommend using the version in your home folder (Global Properties). Gradle looks for '''gradle.properties''' files in this sequence:<br />
* <code>gradle.properties</code> in project root directory.<br />
* <code>gradle.properties</code> in GRADLE_USER_HOME directory.<br />
* system properties, e.g. when <code>-Dgradle.user.home</code> is set on the command line.<br />
<br />
Properties from one file will override the properties from the previous ones (so file in gradle user home has precedence over the others, and file in sub-project has precedence over the one in project root).<br />
<br />
Reference: https://gradle.org/docs/current/userguide/build_environment.html<br />
<br />
<source lang="java">machine raw.githubusercontent.com<br />
nielsen_user=<Nielsen App SDK client><br />
nielsen_authCode=<Auth token></source><br />
<br />
=== Credentials ===<br />
Obtain credentials '''[[Digital Downloads| → here ←]]'''<br />
<br />
=== Add Nielsen Maven Repository ===<br />
<br />
Please add the Nielsen maven repository inside the repositories section of your app's module build.gradle file like below:<br />
<br />
<source lang="java">android{ <br />
repositories {<br />
//Copy below code inside repository section of app’s build.gradle file<br />
maven { url 'https://raw.githubusercontent.com/NielsenDigitalSDK/nielsenappsdk-android/master/'<br />
credentials {<br />
//Make sure you add nielsen_user and nielsen_passwd with respective nielsen provided<br />
// values to global gradle.properties file<br />
username = project.property("nielsen_user")<br />
password = project.property("nielsen_authCode")<br />
}<br />
authentication {<br />
basic(BasicAuthentication)<br />
}<br />
}<br />
}<br />
<br />
}</source><br />
<br />
=== Add gradle dependency ===<br />
<br />
Please add Nielsen app SDK as compile time dependency inside build.gradle file as below<br />
==== grade 4.x and above ====<br />
For gradle version starting with 4.x add below line inside dependencies section of build.gradle file.<br />
<br />
<source lang="java">implementation 'com.nielsenappsdk:${flavour}:${version}'</source><br />
==== grade prior to 4.x ====<br />
For gradle version previous to 4.x add below line inside dependencies section of build.gradle file.<br />
<br />
<source lang="java">compile 'com.nielsenappsdk:${flavour}:${version}'</source><br />
==== Version Control ====<br />
Please replace ${version} with required Nielsen App SDK release version given in below table Please replace ${flavour} with one of below flavour type value<br />
<br />
{| class="wikitable"<br />
|-<br />
!Flavour Type !! Description !! Version<br />
|-<br />
| global||For Global Clients (US/AU/TH/CZ)||6.1.0.1,<br />
|-<br />
|agf||For AGF(EU) Clients||6.1.0.1<br />
|-<br />
|vri||For VRI| based nielsen app sdk clients (Japan)||6.1.0.1<br />
|}<br />
<br />
Below is an example for dependency<br />
<br />
<source lang="java"><br />
dependencies { ....<br />
implementation 'com.nielsenappsdk:global:6.1.0.1'<br />
}<br />
</source><br />
<br />
=== Ensuring you have the latest release information ===<br />
It is recommended to use the most recent version of the NielsenSDK by using the following:<br />
<source lang="java"><br />
dependencies { ....<br />
implementation 'com.nielsenappsdk:global:+'<br />
}<br />
</source><br />
<br />
<br />
In addition, please add below gradle task inside your <code> build.gradle (Module:app)</code> or <code>build.gradle(Project:My-app)</code> file to fetch latest release details of nielsen app sdk as below:<br />
<br />
<blockquote>Please note: The <code> build.gradle (Module:app)</code> can overwrite the <code>build.gradle(Project:My-app)</code>.</blockquote><br />
<br />
<source lang="java">task NielsenSdkReleaseCheck {<br />
def login_details = project.property("nielsen_user")+":"+project.property("nielsen_authCode")<br />
def p = ['curl', '-u',login_details ,"https://raw.githubusercontent.com/NielsenDigitalSDK/nielsenappsdk-android/master/com/nielsenappsdk/global/NielsenAppSdk-ReadMe.md"].execute().text<br />
project.logger.log(LogLevel.ERROR,p)<br />
}<br />
preBuild.dependsOn('NielsenSdkReleaseCheck')</source><br />
<br />
=== Sync ===<br />
<br />
If you are finished with all previous steps then you can sync your build.gradle and after successful build you are ready to use Nielsen App SDK library in your code.<br />
<br />
<br ></div>AlexGutierrezhttps://engineeringportal.nielsen.com//w/index.php?title=Digital_Measurement_Browser_Simplified_API&diff=3459Digital Measurement Browser Simplified API2019-02-20T21:59:30Z<p>AlexGutierrez: </p>
<hr />
<div>{{Breadcrumb|}} {{Breadcrumb|Digital}} {{Breadcrumb|DCR & DTVR}} {{CurrentBreadcrumb}}<br />
[[Category:Digital]]<br />
== Overview ==<br />
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.<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:<br />
*Application launch events and how long app was running<br />
*Time of viewing a sub section / page in the application.<br />
== Prerequisites ==<br />
To start using the Simplified SDK for Browser, you will need an '''Appid.''' This is a Unique ID assigned to the player/site and configured by product.<br />
If you do not have an AppID , please contact our SDK sales support team.<br />
Refer to [[Digital Measurement Onboarding]] guide for information on how to get a Nielsen App SDK and appid.<br />
<br />
== Simplified SDK API ==<br />
As part of making the SDK more user friendly and reduce the number of app integration touch points, Nielsen has designed a simple interface to pass metadata to the sdk while reducing the number of API calls. The new <code> trackevent() </code> API has been implemented as a wrapper for the existing SDK and will be responsible for handling new API calls, performing validation, and translation of 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.<br />
[[File:SimplifiedAPI_vs_StandardAPI_New.jpg|2048px|link=http://dayrhesdkp001z.enterprisenet.org/w/images/9/91/SimplifiedAPI_vs_StandardAPI_New.jpg]]<br />
[http://dayrhesdkp001z.enterprisenet.org/w/images/9/91/SimplifiedAPI_vs_StandardAPI_New.jpg Click here to zoom in on image]<br />
== Manages the order of metadata (ad vs content and playheads) ==<br />
Existing API has a number of methods used for reporting player and application state changes to the SDK. Order of calls is important for the SDK in the existing API. In the new enhanced 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 existing API in separate calls will be provided in one single call. SDK will analyse the data received in the dictionary object, compare it with the data received previously and generate a sequence of calls for the existing API.<br />
== Everything passed via trackevent() ==<br />
The New API method supports the following event types:<br />
{| class="wikitable"<br />
!Key<br />
!Description<br />
|-<br />
|'''playhead'''||<br />
It is used to pass content, ad or static metadata, the current playhead value, UNIX timestamp or id3 payload, ott information to the SDK.<br />
|-<br />
|'''pause'''||<br />
This event should be passed when the<br />
content playback is paused. <br />
|-<br />
|'''complete'''||<br />
It is called when session is completed or ends.<br />
|-<br />
|'''adStop'''||<br />
Should be called at the end of each ad. This event type is required to handle the case when advertisements could not be distinguished, as its assetId is the same.<br />
|}<br />
<br />
__TOC__<br />
<br />
== Prerequisites ==<br />
To get started, an App ID is needed. The App ID is a unique ID assigned to the player/site/app. This will be provided upon starting the integration.<br />
<syntaxhighlight lang="javascript">'PXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'</syntaxhighlight><br />
<br />
== Configure SDK ==<br />
There are two steps required for configuring the SDK:<br />
* Add Static Queue Snippet<br />
* Create SDK Instance<br />
=== Add Static Queue Snippet ===<br />
Add the following script tag to the website:<br />
<syntaxhighlight lang="javascript"><br />
<script type = "text/javascript" ><br />
/***************** Static Queue Snippet *********************/<br />
! function(t, n) {<br />
t[n] = t[n] || {<br />
nlsQ: function(e, c, o, r, s, i) {<br />
return s = t.document,<br />
r = s.createElement("script"),<br />
r.async = 1,<br />
r.src = ("http:" === t.location.protocol ? "http:" : "https:") + "//cdn-gl.imrworldwide.com/conf/" + e + ".js#name=" + c + "&ns=" + n, i = s.getElementsByTagName("script")[0], i.parentNode.insertBefore(r, i),<br />
t[n][c] = t[n][c] || {<br />
g: o || {},<br />
ggPM: function(e, o, r, s, i) {<br />
(t[n][c].q = t[n][c].q || []).push([e, o, r, s, i])<br />
},<br />
trackEvent: function(e) {<br />
(t[n][c].te = t[n][c].te || []).push(e)<br />
}<br />
}, t[n][c]<br />
}<br />
}<br />
}(window, "NOLBUNDLE"); <br />
</script><br />
</syntaxhighlight><br />
<br />
The static queue snippet allows the SDK APIs to be called while the actual SDK and configuration file are still being downloaded. As the queue can capture all API calls before the download completes, there is no wait time. Once the SDK is available, the API calls will transition from directing to the queue to the SDK seamlessly.<br />
<br />
===Create SDK Instance===<br />
To initialize the SDK, create an SDK instance by making the initialization call:<br />
<br />
==== Initialization API Call ====<br />
<syntaxhighlight lang="javascript"><br />
NOLBUNDLE.nlsQ("<apid>", "<instanceName>",{nol_sdkDebug: "debug", optout: "false"})<br />
</syntaxhighlight><br />
<br><br />
When creating an instance, pass the following values: (<code>nol_sdkDebug</code> and <code>optout</code> are optional)<br />
{| class="wikitable"<br />
|-<br />
! Parameter !! Description !! Values<br />
|-<br />
| apid || Unique ID assigned to player/site || 'PXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'<br />
|-<br />
|instanceName || Name of SDK instance || "any string value"<br />
|-<br />
| nol_sdkDebug || Enables Nielsen console logging. Only required for testing || "{nol_sdkDebug: "debug"})"<br />
|-<br />
|optout || OptOut global parameter (Optional Parameter) || <code>1/0</code> or <code>true/false</code><br />
|}<br />
<br />
==== Example SDK Initialization ====<br />
When the initialization call is made, a unique static configuration file, <apid>.js, will be downloaded based on the apid and will be cached on the user’s browser.<br />
<br />
Once the configuration is downloaded, the SDK itself will be downloaded and initialized. All SDK modules are included in one file: “nlsSDK600.bundle.min.js”.<br />
<br />
More information on OptOut Parameter under [[DCR Video Browser Bundled SDK#Privacy_and_Opt-Out|Privacy and Opt-Out.]]<br />
<br />
=== Example SDK Configuration ===<br />
<br />
The configuration should include the Static Queue Snippet and an SDK Instance for an unique App ID as shown in the example:<br />
<syntaxhighlight lang="javascript"><br />
<script type = "text/javascript" ><br />
/***************** Static Queue Snippet *********************/<br />
! function(t, n) {<br />
t[n] = t[n] || {<br />
nlsQ: function(e, c, o, r, s, i) {<br />
return s = t.document,<br />
r = s.createElement("script"),<br />
r.async = 1,<br />
r.src = ("http:" === t.location.protocol ? "http:" : "https:") + "//cdn-gl.imrworldwide.com/conf/" + e + ".js#name=" + c + "&ns=" + n, i = s.getElementsByTagName("script")[0], i.parentNode.insertBefore(r, i),<br />
t[n][c] = t[n][c] || {<br />
g: o || {},<br />
ggPM: function(e, o, r, s, i) {<br />
(t[n][c].q = t[n][c].q || []).push([e, o, r, s, i])<br />
},<br />
trackEvent: function(e) {<br />
(t[n][c].te = t[n][c].te || []).push(e)<br />
}<br />
}, t[n][c]<br />
}<br />
}<br />
}(window, "NOLBUNDLE"); <br />
<br />
// Created SDK Instance<br />
var nSdkInstance = NOLBUNDLE.nlsQ("XXXXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXX", "nlsnInstance", {<br />
nol_sdkDebug: "debug",<br />
outout: "false"<br />
});<br />
</script></syntaxhighlight><br />
<br />
== Simplified API Syntax ==<br />
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. <br />
<br />
Main API call for the new NielsenEventTracker API:<br />
<br/><br />
<syntaxhighlight lang="java"> nSdkInstance.trackEvent({metadata})<br />
</syntaxhighlight><br />
<br />
=== Handling JSON Metadata ===<br />
Parameter “data” is a JSON object with many key-value pairs that holds all information required by SDK.<br />
<br />
Format of input object is the following:<br />
<syntaxhighlight lang="json"><br />
{ <br />
"event": <event identifier>,<br />
"type": <type of metadata>,<br />
"metadata":{ <br />
"content": <content metadata object>,<br />
"ad": <ad metadata object>,<br />
"static": <static metadata object><br />
},<br />
"playheadPosition":<playhead value | UNIX timestamp (seconds since Jan-1-1970 UTC) >,<br />
"id3Data": <id3 payload>,<br />
}<br />
</syntaxhighlight><br />
<br><br />
=== Event Types ===<br />
The New API method supports the following event types:<br />
{| class="wikitable"<br />
!Key<br />
!Description<br />
|-<br />
|'''playhead'''||<br />
It is used to pass content, ad or static metadata, the current playhead value, Unix timestamp or id3 payload, ott information to the SDK.<br />
|-<br />
|'''pause'''||<br />
This event should be used when<br />
content playback is paused. <br > (Pause is detected by SDK automatically when the time gap between commands is more than 30 minutes.)<br />
|-<br />
|'''complete'''||<br />
It is called when session is completed or ends.<br />
|-<br />
|'''adStop'''||<br />
Should be called at the end of each ad. This event type is required to handle the case when advertisements could not be distinguished, as its assetId is the same.<br />
|}<br />
<br><br />
DCR and DTVR require various levels of data. Please select the TAB of the product you are interested in reviewing.<br />
{{DCRDTVRTabs<br />
|DCR=<br />
=== Digital Content Ratings===<br />
<table><br />
<tr><br />
<th> Parameter<br />
</th><br />
<th> <b>Description</b><br />
</th><br />
<th> <b>Supported values</b><br />
</th><br />
<th> <b>Example</b><br />
</th></tr><br />
<tr><br />
<td> <b>event</b><br />
</td><br />
<td> Event identifier<br />
</td><br />
<td><br />
<p><code> String:<br />
playhead,pause,complete,<br />
adStop</code><br />
</p><br />
</td><br />
<td><syntaxhighlight lang="swift">"event":"playhead"</syntaxhighlight><br />
</td></tr><br />
<tr><br />
<td> <b>type</b><br />
</td><br />
<td> Determines the metadata object <br />
that should be used for crediting.<br />
</td><br />
<td><br />
<p><code> String:<br /><br />
content,<br />
ad,<br />
static</code><br />
</p><br />
</td><br />
<td><syntaxhighlight lang="swift">"type":"content"</syntaxhighlight><br />
</td></tr><br />
<tr><br />
<td> <b>metadata</b><br />
</td><br />
<td> Object that holds metadata values of specific types<br /><br />
<p><span style="color:blue"> Detailed in tables below</span><br />
</p><br />
</td><br />
<td> <code>Object</code><br />
</td><br />
<td><syntaxhighlight lang="swift"><br />
"metadata":{ <br />
"content": <content metadata object>,<br />
"ad": <ad metadata object>,<br />
"static": <static metadata object><br />
},<br />
</syntaxhighlight><br />
</td></tr><br />
<tr><br />
<td> <b>playheadPosition</b><br />
</td><br />
<td> Playhead value in seconds or Unix timestamp<br />
</td><br />
<td> <code>String</code><br />
</td><br />
<td><br />
<p>Position value is Unix timestamp:<br />
<code><br />
"playheadPosition":"1543437655"</code><br />
</p><p>Position value is playhead:<br />
<code><br />
"playheadPosition":"10"</code><br />
</p><br />
</td></tr><br />
</table><br />
=== Content Metadata ===<br />
Content metadata sent for every playheadposition update.<br />
<table><br />
<tr><br />
<th> Keys </th><br />
<th> Description </th><br />
<th> Example </th><br />
<th> Required<br />
</th></tr><br />
<tr><br />
<td>'''assetName''' </td><br />
<td> name of program (100 character limit) </td><br />
<td> <code>"MyTest789"</code> </td><br />
<td> Yes<br />
</td></tr><br />
<tr><br />
<td> '''assetid''' </td><br />
<td> unique ID assigned to asset </td><br />
<td> custom </td><br />
<td> Yes<br />
</td></tr><br />
<tr><br />
<td> '''length''' </td><br />
<td> length of content in seconds </td><br />
<td> <code>seconds</code> (86400 for live stream) </td><br />
<td> Yes<br />
</td></tr><br />
<tr><br />
<td> '''program''' </td><br />
<td>name of program (100 character limit) </td><br />
<td> custom </td><br />
<td> Yes<br />
</td></tr><br />
<tr><br />
<td> '''segB''' </td><br />
<td> custom segment B +</td><br />
<td> custom </td><br />
<td><br />
</td></tr><br />
<tr><br />
<td> '''segC''' </td><br />
<td> custom segment C +</td><br />
<td> custom </td><br />
<td><br />
</td></tr><br />
<tr><br />
<td> '''title''' </td><br />
<td>name of program (100 character limit) </td><br />
<td> custom </td><br />
<td> Yes<br />
</td></tr><br />
<tr><br />
<td>'''type'''</td><br />
<td><code>'content', 'ad', 'static'</code></td><br />
<td> <code> 'content'</code> </td><br />
<td> Yes<br />
</td></tr><br />
<tr><br />
<td>'''section''' </td><br />
<td> Unique Value assigned to page/site section </td><br />
<td> HomePage </td><br />
<td> Yes<br />
</td></tr><br />
<tr><br />
<td> '''airdate''' </td><br />
<td> the airdate in the linear TV ++</td><br />
<td> YYYYMMDD HH24:MI:SS </td><br />
<td> Yes<br />
</td></tr><br />
<tr><br />
<td> '''isfullepisode''' </td><br />
<td> full episode flag </td><br />
<td> <code>"y"</code>- full episode, <code>"n"</code>- non full episode </td><br />
<td> Yes<br />
</td></tr><br />
<tr><br />
<td> '''crossId1''' </td><br />
<td> Gracenote ID or Enterprise ID </td><br />
<td> custom </td><br />
<td> Yes<br />
</td></tr><br />
<tr><br />
<td> '''crossId2 '''</td><br />
<td> content originator or network (only required for distributors) </td><br />
<td> custom </td><br />
<td><br />
</td></tr><br />
<tr><br />
<td> <b>adloadtype</b><br />
</td><br />
<td> linear vs dynamic ad model<br />
</td><br />
<td> 1=Linear<br />
2=Dynamic Ads<br />
</td><br />
<td>custom<br />
</td></tr><br />
</table><br />
+ '''Custom segments''' (segB and segC) can be used to aggregate video and/or static content within a single Brand to receive more granular reports within a brand.<br><br />
++ Acceptable '''Air Date''' Formats:<br />
<syntaxhighlight lang="json"><br />
YYYYMMDD HH24:MI:SS<br />
YYYYMMDDHH24MISS<br />
YYYY-MM-DDTHH:MI:SS<br />
YYYY-MM-DDHH:MI:SS <br />
YYYYMMDDHH:MI:SS<br />
MM-DD-YYYY<br />
YYYYMMDD HH:MI:SS<br />
</syntaxhighlight><br />
<br><br />
For USA all times should be EST, for all other countries Local Time.<br />
Below is a sample event for DCR. If no ad or static values, these can be left as blank/null.<br />
<syntaxhighlight lang="json"><br />
{ <br />
"event": "playhead",<br />
"type": "content",<br />
"metadata": { <br />
"content":{<br />
"assetName":"Big Buck Bunny",<br />
"assetid":"B66473",<br />
"length":"3600",<br />
"program":"MyProgram",<br />
"segB":"CustomSegmentValueB",<br />
"segC":"segmentC",<br />
"title":"S2,E3",<br />
"type":"content",<br />
"section":"cloudApi_app",<br />
"airdate":"20180120 10:00:00",<br />
"isfullepisode":"y",<br />
"crossId1":"Standard Episode ID",<br />
"crossId2" :"Content Originator",<br />
"adloadtype":"2"},<br />
"ad": {},<br />
"static": {}<br />
},<br />
"playheadPosition": "",<br />
}<br />
</syntaxhighlight><br />
<br />
|DTVR=<br />
=== Digital TV Ratings info ===<br />
<table><br />
<tr><br />
<th> Parameter<br />
</th><br />
<th> <b>Description</b><br />
</th><br />
<th> <b>Supported values</b><br />
</th><br />
<th> <b>Example</b><br />
</th></tr><br />
<tr><br />
<td> <b>event</b><br />
</td><br />
<td> Event identifier<br />
</td><br />
<td><br />
<p><code> String:<br />
playhead,pause,complete,<br />
adStop</code><br />
</p><br />
</td><br />
<td><syntaxhighlight lang="swift">"event":"playhead"</syntaxhighlight><br />
</td></tr><br />
<tr><br />
<td> <b>type</b><br />
</td><br />
<td> Determines the metadata object that should be used for crediting.<br />
</td><br />
<td><br />
<p><code> String:<br /><br />
content,<br />
ad,<br />
static</code><br />
</p><br />
</td><br />
<td><syntaxhighlight lang="swift">"type":"content"</syntaxhighlight><br />
</td></tr><br />
<tr><br />
<td> <b>adModel</b><br />
</td><br />
<td> linear vs dynamic ad model<br />
</td><br />
<td> 1=Linear<br />
2=Dynamic Ads<br />
</td><br />
<td>custom<br />
</td></tr><br />
<tr><br />
<td> <b>channelName</b><br />
</td><br />
<td> Any string representing the.channel/stream<br />
</td><br />
<td> <code>Object</code><br />
</td><br />
<td>custom<br />
</td></tr><br />
<tr><br />
<td> <b>playheadPosition</b><br />
</td><br />
<td> Playhead value in seconds or Unix timestamp<br />
</td><br />
<td> <code>String</code><br />
</td><br />
<td><br />
<p>Position value is Unix timestamp:<br />
<code><br />
"playheadPosition":"1543437655"</code><br />
</p><p>Position value is playhead:<br />
<code><br />
"playheadPosition":"10"</code><br />
</p><br />
</td></tr><br />
<tr><br />
<td> <b>id3Data</b><br />
</td><br />
<td> Nielsen ID3 payload string<br />
</td><br />
<td> <code>Object</code><br />
</td><br />
<td><br />
<p><code><br />
id3Data: www.nielsen.com/065H2g6E7ZyQ5UdmMAbbpg<br />
==/_EMc37zfVgq_8KB7baUYfg==/ADQCAmgV1Xyvnynyg60<br />
kZO_Ejkcn2KLSrTzyJpZZ-QeRn8GpMGTWI7-HrEKzghxyzC<br />
yBEoIDv2kA2g1QJmeYOl5GnwfrLDVK2bNLTbQxr1z9VBfxa<br />
hBcQP5tqbjhyMzdVqrMKuvvJO1jhtSXa9AroChb11ZUnG1W<br />
VJx2O4M=/33648/22847/00</code><br />
</p><br />
<tr><br />
<br />
</td></tr><br />
</table><br />
Below is a sample event for DTVR. If no ad or static values, these can be left as blank/null.<br />
<syntaxhighlight lang="json"><br />
{ <br />
"event": "playhead",<br />
"type": "content",<br />
"metadata": { <br />
"content":{<br />
"adModel":"1",<br />
"channelname":"channel1"<br />
},<br />
"ad": {},<br />
"static": {}<br />
},<br />
"playheadPosition": "",<br />
"id3Data": "www.nielsen.com/065H2g6E7ZyQ5UdmMAbbpg==/_<br />
EMc37zfVgq_8KB7baUYfg==/ADQCAmgV1Xyvnynyg60kZO_Ejkcn<br />
2KLSrTzyJpZZ-QeRn8GpMGTWI7-HrEKzghxyzCyBEoIDv2kA2g1Q<br />
JmeYOl5GnwfrLDVK2bNLTbQxr1z9VBfxahBcQP5tqbjhyMzdVqrMK<br />
uvvJO1jhtSXa9AroChb11ZUnG1WVJx2O4M=/33648/22847/00"<br />
}<br />
</syntaxhighlight><br />
|DCRDTVR=<br />
=== Applies to DCR and DTVR ===<br />
<table><br />
<tr><br />
<th> Parameter<br />
</th><br />
<th> <b>Description</b><br />
</th><br />
<th> <b>Supported values</b><br />
</th><br />
<th> <b>Example</b><br />
</th></tr><br />
<tr><br />
<td> <b>event</b><br />
</td><br />
<td> Event identifier<br />
</td><br />
<td><br />
<p><code> String:<br />
playhead,<br />
pause,<br />
complete,<br />
adStop</code><br />
</p><br />
</td><br />
<td><syntaxhighlight lang="swift">"event":"playhead"</syntaxhighlight><br />
</td></tr><br />
<tr><br />
<td> <b>type</b><br />
</td><br />
<td> Determines the metadata object that should be used for crediting.<br />
</td><br />
<td><br />
<p><code> String:<br /><br />
content,<br />
ad,<br />
static</code><br />
</p><br />
</td><br />
<td><syntaxhighlight lang="swift">"type":"content"</syntaxhighlight><br />
</td></tr><br />
<tr><br />
<td> <b>metadata</b><br />
</td><br />
<td> Object that holds metadata values of specific types<br /><br />
<p><span style="color:blue"> Detailed in tables below</span><br />
</p><br />
</td><br />
<td> <code>Object</code><br />
</td><br />
<td><syntaxhighlight lang="swift"><br />
"metadata":{ <br />
"content": <content metadata object>,<br />
"ad": <ad metadata object>,<br />
"static": <static metadata object><br />
},<br />
</syntaxhighlight><br />
</td></tr><br />
<tr><br />
<td> <b>playheadPosition</b><br />
</td><br />
<td> Playhead value in seconds or Unix timestamp<br />
</td><br />
<td> <code>String</code><br />
</td><br />
<td><br />
<p>Position value is Unix timestamp:<br />
<code><br />
"playheadPosition":"1543437655"</code><br />
</p><p>Position value is playhead:<br />
<code><br />
"playheadPosition":"10"</code><br />
</p><br />
</td></tr><br />
<tr><br />
<td> <b>id3Data</b><br />
</td><br />
<td> Nielsen ID3 payload string<br />
</td><br />
<td> <code>Object</code><br />
</td><br />
<td><br />
<p><code><br />
id3Data: www.nielsen.com/065H2g6E7ZyQ5UdmMAbbpg<br />
==/_EMc37zfVgq_8KB7baUYfg==/ADQCAmgV1Xyvnynyg60<br />
kZO_Ejkcn2KLSrTzyJpZZ-QeRn8GpMGTWI7-HrEKzghxyzC<br />
yBEoIDv2kA2g1QJmeYOl5GnwfrLDVK2bNLTbQxr1z9VBfxa<br />
hBcQP5tqbjhyMzdVqrMKuvvJO1jhtSXa9AroChb11ZUnG1W<br />
VJx2O4M=/33648/22847/00</code><br />
</p><br />
</td></tr><br />
<tr><br />
<td> <b>ottData</b><br />
</td><br />
<td> Object that holds ott information<br />
</td><br />
<td> <code>Object</code><br />
</td><br />
<td><syntaxhighlight lang="swift"><br />
ottData:{<br />
ottStatus:1,<br />
ottType:casting,<br />
ottDevice:chromecast,<br />
ottDeviceName:Google ChromeCast,<br />
ottDeviceID:1234,<br />
ottDeviceModel:ChromeCast,<br />
ottDeviceVersion:1.0.0<br />
}<br />
</syntaxhighlight><br />
</td></tr><br />
</table><br />
=== Content Metadata ===<br />
Content metadata sent for every playheadposition update.<br />
<table><br />
<tr><br />
<th> Keys </th><br />
<th> Description </th><br />
<th> Example </th><br />
<th> Required<br />
</th></tr><br />
<tr><br />
<td> '''assetid''' </td><br />
<td> unique asset ID for DCR; can be set to "0" for DTVR </td><br />
<td> <code>EP732481</code> </td><br />
<td> Yes<br />
</td></tr><br />
<tr><br />
<td> '''length''' </td><br />
<td> length of content in seconds </td><br />
<td> <code>seconds</code> (86400 for live stream) </td><br />
<td> Yes<br />
</td></tr><br />
<tr><br />
<td>'''type'''</td><br />
<td><code>'content', 'ad', 'static'</code></td><br />
<td> <code> 'content'</code> </td><br />
<td> Yes<br />
</td></tr><br />
<tr><br />
<td> <b>adModel</b><br />
</td><br />
<td> linear vs dynamic ad model<br />
</td><br />
<td> 1=Linear<br />
2=Dynamic Ads<br />
</td><br />
<td>custom<br />
</td></tr><br />
<tr><br />
<td> <b>adloadtype</b><br />
</td><br />
<td> DCR Ad Model<br />
</td><br />
<td> 1=Linear<br />
2=Dynamic Ads<br />
</td><br />
<td>custom<br />
</td></tr><br />
</table><br />
+ '''Custom segments''' (segB and segC) can be used to aggregate video and/or static content within a single Brand to receive more granular reports within a brand.<br><br />
++ Acceptable '''Air Date''' Formats:<br />
<syntaxhighlight lang="json"><br />
YYYYMMDD HH24:MI:SS<br />
YYYYMMDDHH24MISS<br />
YYYY-MM-DDTHH:MI:SS<br />
YYYY-MM-DDHH:MI:SS <br />
YYYYMMDDHH:MI:SS<br />
MM-DD-YYYY<br />
YYYYMMDD HH:MI:SS<br />
</syntaxhighlight><br />
<br><br />
Below is a sample event for DCR/DTVR joint integration. If no ad or static values, these can be left as blank/null.<br />
<syntaxhighlight lang="json"><br />
{ <br />
"event": "playhead",<br />
"type": "content",<br />
"metadata": { <br />
"content":{<br />
"type":"content",<br />
"assetid":"0",<br />
"length":"86400",<br />
"adModel":"1",<br />
"adloadtype":"1"},<br />
"ad": {},<br />
"static": {}<br />
},<br />
"playheadPosition": "",<br />
"id3Data": "www.nielsen.com/065H2g6E7ZyQ5UdmMAbbpg==/_<br />
EMc37zfVgq_8KB7baUYfg==/ADQCAmgV1Xyvnynyg60kZO_Ejkcn<br />
2KLSrTzyJpZZ-QeRn8GpMGTWI7-HrEKzghxyzCyBEoIDv2kA2g1Q<br />
JmeYOl5GnwfrLDVK2bNLTbQxr1z9VBfxahBcQP5tqbjhyMzdVqrMK<br />
uvvJO1jhtSXa9AroChb11ZUnG1WVJx2O4M=/33648/22847/00"<br />
}<br />
</syntaxhighlight><br />
}}<br />
<br />
=== Ad Metadata ===<br />
The ad metadata (if applicable) should be passed for each individual ad, if ads are available during or before the stream begins.<br />
<br/><br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Example<br />
|-<br />
| assetid || unique ID assigned to ad || custom <br>(no [[Special Characters]]) || <code>'AD1234'</code><br />
|-<br />
| title || unique name assigned to ad || custom ||<code>'ADtitle'</code><br />
|-<br />
|adldx || Ad Index (*See Note below*) || custom || <code> "66478364" </code><br />
|-<br />
| type || type of ad || 'preroll', 'midroll', or 'postroll' || <code>'preroll'</code><br />
|-<br />
|length || length of ad || In Seconds ||<code> '20' </code><br />
|}<br />
=== Ad Metadata Sample ===<br />
<syntaxhighlight lang="json"><br />
{<br />
"ad": {<br />
"assetid":"AD12345",<br />
"title":"ADTestTitle",<br />
"adldx":"1",<br />
"type":"preroll",<br />
"length":"20"<br />
},<br />
}<br />
</syntaxhighlight><br />
=== Managing Ads ===<br />
If there is an Ad block within the playing content (such as a midroll) you need to:<br />
* Reset the playhead position to 0 for each ad.<br />
* Call the '''adStop''' event at the end of each ad or increment the adldx<br />
<br />
The Simplified SDK will can automatically detect the change from ad to content, or even ad to ad if the assetID changes; however, there could be situations where the same ad is played back to back. You can either increment/change the '''adldx value''', and/or call adStop at the end of each Ad.<br />
<br />
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 Nielsen Simplified API will support a new parameter for ad metadata: '''adIdx.''' This parameter is just 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.<br />
<br />
<syntaxhighlight lang="swift"><br />
// Example of passing both values<br />
self.data.updateValue("adStop", forKey: "event")<br />
self.data.updateValue("223", forKey: "adldx")<br />
self.nielsenEventTracker.trackEvent(data)<br />
</syntaxhighlight><br />
<br />
<br />
{{Template:Browser_Privacy_and_Opt-Out}}<br />
<br />
== Going Live ==<br />
After the integration has been certified, users will need to make a couple of updates to the initialization call to ensure that player will be measured properly.<br />
Disable debug logging by deleting {nol_sdkDebug: 'DEBUG'} from initialization call.<br />
Example Production Initialization Call<br />
<br />
<syntaxhighlight lang="javascript"><br />
var nSdkInstance = NOLBUNDLE.nlsQ("XXXXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXX", "nlsnInstance", { outout: "false"});<br />
</syntaxhighlight><br />
<br />
== Sample Code ==<br />
This code is for example purposes only to demonstrate a very simple video implementation, with a single preroll and single content block.<br />
<syntaxhighlight lang=javascript><br />
<script ><br />
// Setup videojs sample player<br />
var nielsenid = document.getElementById("my-sdkplayer");<br />
var coolPlayer = videojs('my-sdkplayer', {<br />
controls: true,<br />
autoplay: true,<br />
preload: 'auto',<br />
});<br />
var prevPos = null;<br />
<br />
coolPlayer.preroll({<br />
src: {<br />
src: "assets/CleaningCrew.mp4",<br />
type: "video/mp4"<br />
},<br />
href: "http://videojs.com",<br />
adsOptions: {<br />
debug: true<br />
}<br />
});<br />
<br />
videojs('my-sdkplayer').ready(function() {<br />
// EXAMPLE: Start playing the video.<br />
<br />
coolPlayer.play();<br />
var obj = {<br />
event: 'playhead', // playhead,pause,complete,adstop<br />
type: 'content',<br />
playheadPosition: '0',<br />
metadata: {<br />
content: {<br />
type: 'content',<br />
assetid: 'VID98-2B88',<br />
program: 'Put a programname here ',<br />
section: 'section name here'<br />
title: 'S01E02',<br />
length: '180',<br />
airdate: '20190101 10:01:00',<br />
isfullepisode: 'y',<br />
crossId1: 'Standard Episode ID',<br />
crossID2: 'Content Originator',<br />
adloadtype: '2'<br />
},<br />
ad: {},<br />
static: {}<br />
}<br />
};<br />
<br />
coolPlayer.on('timeupdate', function() {<br />
var getcurrentTime = coolPlayer.currentTime();<br />
var intPlayedTime = parseInt(getcurrentTime, 10);<br />
if (intPlayedTime > 0 && intPlayedTime !== prevPos) {<br />
// check for playhead change so it's only called once per second <br />
console.log('PLAYHEAD EVENT **************************************************************');<br />
obj.event = 'playhead';<br />
obj.type = 'content';<br />
obj.metadata.ad = {};<br />
prevPos = intPlayedTime;<br />
obj.playheadPosition = Math.round(getcurrentTime);<br />
nSdkInstance.trackEvent(obj);<br />
}<br />
});<br />
<br />
this.on('adstart', function() {<br />
console.log('ADSTART EVENT **************************************************************');<br />
obj.type = 'ad';<br />
obj.metadata.ad = {<br />
type: 'preroll',<br />
assetid: 'AD9455',<br />
adldx: '1',<br />
program: 'Cant Skip Campaign',<br />
title: 'Cleaning Crew',<br />
length: '36',<br />
};<br />
nSdkInstance.trackEvent(obj);<br />
});<br />
<br />
this.on('contentresumed', function() {<br />
console.log('CONTENT RESUMED EVENT **************************************************************');<br />
obj.type = 'content';<br />
obj.event = 'playhead';<br />
obj.metadata.ad = {};<br />
nSdkInstance.trackEvent(obj);<br />
});<br />
<br />
this.on('pause', function() {<br />
console.log('PAUSE EVENT **************************************************************');<br />
obj.event = 'pause';<br />
obj.playheadPosition = Math.round(coolPlayer.currentTime());<br />
nSdkInstance.trackEvent(obj);<br />
});<br />
<br />
this.on('adtimeupdate', function() {<br />
var getcurrentTime = coolPlayer.currentTime();<br />
var intPlayedTime = parseInt(getcurrentTime, 10);<br />
if (intPlayedTime > 0 && intPlayedTime !== prevPos) {<br />
// check for playhead change so it's only called once per second <br />
console.log('AD PLAYHEAD EVENT **************************************************************');<br />
console.log(Math.round(getcurrentTime));<br />
prevPos = intPlayedTime;<br />
obj.playheadPosition = Math.round(getcurrentTime);<br />
nSdkInstance.trackEvent(obj);<br />
};<br />
});<br />
<br />
this.on('adend', function() {<br />
console.log('AD STOP EVENT **************************************************************');<br />
obj.event = 'adstop';<br />
nSdkInstance.trackEvent(obj);<br />
});<br />
<br />
this.on('ended', function() {<br />
console.log('COMPLETE EVENT **************************************************************');<br />
obj.event = 'complete';<br />
nSdkInstance.trackEvent(obj);<br />
});<br />
});<br />
<br />
</script><br />
</syntaxhighlight></div>AlexGutierrezhttps://engineeringportal.nielsen.com//w/index.php?title=Digital_Measurement_Browser_Simplified_API&diff=3458Digital Measurement Browser Simplified API2019-02-20T21:56:00Z<p>AlexGutierrez: Updates to DCR/DTVR content metadata for assetID requirement</p>
<hr />
<div>{{Breadcrumb|}} {{Breadcrumb|Digital}} {{Breadcrumb|DCR & DTVR}} {{CurrentBreadcrumb}}<br />
[[Category:Digital]]<br />
== Overview ==<br />
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.<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:<br />
*Application launch events and how long app was running<br />
*Time of viewing a sub section / page in the application.<br />
== Prerequisites ==<br />
To start using the Simplified SDK for Browser, you will need an '''Appid.''' This is a Unique ID assigned to the player/site and configured by product.<br />
If you do not have an AppID , please contact our SDK sales support team.<br />
Refer to [[Digital Measurement Onboarding]] guide for information on how to get a Nielsen App SDK and appid.<br />
<br />
== Simplified SDK API ==<br />
As part of making the SDK more user friendly and reduce the number of app integration touch points, Nielsen has designed a simple interface to pass metadata to the sdk while reducing the number of API calls. The new <code> trackevent() </code> API has been implemented as a wrapper for the existing SDK and will be responsible for handling new API calls, performing validation, and translation of 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.<br />
[[File:SimplifiedAPI_vs_StandardAPI_New.jpg|2048px|link=http://dayrhesdkp001z.enterprisenet.org/w/images/9/91/SimplifiedAPI_vs_StandardAPI_New.jpg]]<br />
[http://dayrhesdkp001z.enterprisenet.org/w/images/9/91/SimplifiedAPI_vs_StandardAPI_New.jpg Click here to zoom in on image]<br />
== Manages the order of metadata (ad vs content and playheads) ==<br />
Existing API has a number of methods used for reporting player and application state changes to the SDK. Order of calls is important for the SDK in the existing API. In the new enhanced 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 existing API in separate calls will be provided in one single call. SDK will analyse the data received in the dictionary object, compare it with the data received previously and generate a sequence of calls for the existing API.<br />
== Everything passed via trackevent() ==<br />
The New API method supports the following event types:<br />
{| class="wikitable"<br />
!Key<br />
!Description<br />
|-<br />
|'''playhead'''||<br />
It is used to pass content, ad or static metadata, the current playhead value, UNIX timestamp or id3 payload, ott information to the SDK.<br />
|-<br />
|'''pause'''||<br />
This event should be passed when the<br />
content playback is paused. <br />
|-<br />
|'''complete'''||<br />
It is called when session is completed or ends.<br />
|-<br />
|'''adStop'''||<br />
Should be called at the end of each ad. This event type is required to handle the case when advertisements could not be distinguished, as its assetId is the same.<br />
|}<br />
<br />
__TOC__<br />
<br />
== Prerequisites ==<br />
To get started, an App ID is needed. The App ID is a unique ID assigned to the player/site/app. This will be provided upon starting the integration.<br />
<syntaxhighlight lang="javascript">'PXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'</syntaxhighlight><br />
<br />
== Configure SDK ==<br />
There are two steps required for configuring the SDK:<br />
* Add Static Queue Snippet<br />
* Create SDK Instance<br />
=== Add Static Queue Snippet ===<br />
Add the following script tag to the website:<br />
<syntaxhighlight lang="javascript"><br />
<script type = "text/javascript" ><br />
/***************** Static Queue Snippet *********************/<br />
! function(t, n) {<br />
t[n] = t[n] || {<br />
nlsQ: function(e, c, o, r, s, i) {<br />
return s = t.document,<br />
r = s.createElement("script"),<br />
r.async = 1,<br />
r.src = ("http:" === t.location.protocol ? "http:" : "https:") + "//cdn-gl.imrworldwide.com/conf/" + e + ".js#name=" + c + "&ns=" + n, i = s.getElementsByTagName("script")[0], i.parentNode.insertBefore(r, i),<br />
t[n][c] = t[n][c] || {<br />
g: o || {},<br />
ggPM: function(e, o, r, s, i) {<br />
(t[n][c].q = t[n][c].q || []).push([e, o, r, s, i])<br />
},<br />
trackEvent: function(e) {<br />
(t[n][c].te = t[n][c].te || []).push(e)<br />
}<br />
}, t[n][c]<br />
}<br />
}<br />
}(window, "NOLBUNDLE"); <br />
</script><br />
</syntaxhighlight><br />
<br />
The static queue snippet allows the SDK APIs to be called while the actual SDK and configuration file are still being downloaded. As the queue can capture all API calls before the download completes, there is no wait time. Once the SDK is available, the API calls will transition from directing to the queue to the SDK seamlessly.<br />
<br />
===Create SDK Instance===<br />
To initialize the SDK, create an SDK instance by making the initialization call:<br />
<br />
==== Initialization API Call ====<br />
<syntaxhighlight lang="javascript"><br />
NOLBUNDLE.nlsQ("<apid>", "<instanceName>",{nol_sdkDebug: "debug", optout: "false"})<br />
</syntaxhighlight><br />
<br><br />
When creating an instance, pass the following values: (<code>nol_sdkDebug</code> and <code>optout</code> are optional)<br />
{| class="wikitable"<br />
|-<br />
! Parameter !! Description !! Values<br />
|-<br />
| apid || Unique ID assigned to player/site || 'PXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'<br />
|-<br />
|instanceName || Name of SDK instance || "any string value"<br />
|-<br />
| nol_sdkDebug || Enables Nielsen console logging. Only required for testing || "{nol_sdkDebug: "debug"})"<br />
|-<br />
|optout || OptOut global parameter (Optional Parameter) || <code>1/0</code> or <code>true/false</code><br />
|}<br />
<br />
==== Example SDK Initialization ====<br />
When the initialization call is made, a unique static configuration file, <apid>.js, will be downloaded based on the apid and will be cached on the user’s browser.<br />
<br />
Once the configuration is downloaded, the SDK itself will be downloaded and initialized. All SDK modules are included in one file: “nlsSDK600.bundle.min.js”.<br />
<br />
More information on OptOut Parameter under [[DCR Video Browser Bundled SDK#Privacy_and_Opt-Out|Privacy and Opt-Out.]]<br />
<br />
=== Example SDK Configuration ===<br />
<br />
The configuration should include the Static Queue Snippet and an SDK Instance for an unique App ID as shown in the example:<br />
<syntaxhighlight lang="javascript"><br />
<script type = "text/javascript" ><br />
/***************** Static Queue Snippet *********************/<br />
! function(t, n) {<br />
t[n] = t[n] || {<br />
nlsQ: function(e, c, o, r, s, i) {<br />
return s = t.document,<br />
r = s.createElement("script"),<br />
r.async = 1,<br />
r.src = ("http:" === t.location.protocol ? "http:" : "https:") + "//cdn-gl.imrworldwide.com/conf/" + e + ".js#name=" + c + "&ns=" + n, i = s.getElementsByTagName("script")[0], i.parentNode.insertBefore(r, i),<br />
t[n][c] = t[n][c] || {<br />
g: o || {},<br />
ggPM: function(e, o, r, s, i) {<br />
(t[n][c].q = t[n][c].q || []).push([e, o, r, s, i])<br />
},<br />
trackEvent: function(e) {<br />
(t[n][c].te = t[n][c].te || []).push(e)<br />
}<br />
}, t[n][c]<br />
}<br />
}<br />
}(window, "NOLBUNDLE"); <br />
<br />
// Created SDK Instance<br />
var nSdkInstance = NOLBUNDLE.nlsQ("XXXXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXX", "nlsnInstance", {<br />
nol_sdkDebug: "debug",<br />
outout: "false"<br />
});<br />
</script></syntaxhighlight><br />
<br />
== Simplified API Syntax ==<br />
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. <br />
<br />
Main API call for the new NielsenEventTracker API:<br />
<br/><br />
<syntaxhighlight lang="java"> nSdkInstance.trackEvent({metadata})<br />
</syntaxhighlight><br />
<br />
=== Handling JSON Metadata ===<br />
Parameter “data” is a JSON object with many key-value pairs that holds all information required by SDK.<br />
<br />
Format of input object is the following:<br />
<syntaxhighlight lang="json"><br />
{ <br />
"event": <event identifier>,<br />
"type": <type of metadata>,<br />
"metadata":{ <br />
"content": <content metadata object>,<br />
"ad": <ad metadata object>,<br />
"static": <static metadata object><br />
},<br />
"playheadPosition":<playhead value | UNIX timestamp (seconds since Jan-1-1970 UTC) >,<br />
"id3Data": <id3 payload>,<br />
}<br />
</syntaxhighlight><br />
<br><br />
=== Event Types ===<br />
The New API method supports the following event types:<br />
{| class="wikitable"<br />
!Key<br />
!Description<br />
|-<br />
|'''playhead'''||<br />
It is used to pass content, ad or static metadata, the current playhead value, Unix timestamp or id3 payload, ott information to the SDK.<br />
|-<br />
|'''pause'''||<br />
This event should be used when<br />
content playback is paused. <br > (Pause is detected by SDK automatically when the time gap between commands is more than 30 minutes.)<br />
|-<br />
|'''complete'''||<br />
It is called when session is completed or ends.<br />
|-<br />
|'''adStop'''||<br />
Should be called at the end of each ad. This event type is required to handle the case when advertisements could not be distinguished, as its assetId is the same.<br />
|}<br />
<br><br />
DCR and DTVR require various levels of data. Please select the TAB of the product you are interested in reviewing.<br />
{{DCRDTVRTabs<br />
|DCR=<br />
=== Digital Content Ratings===<br />
<table><br />
<tr><br />
<th> Parameter<br />
</th><br />
<th> <b>Description</b><br />
</th><br />
<th> <b>Supported values</b><br />
</th><br />
<th> <b>Example</b><br />
</th></tr><br />
<tr><br />
<td> <b>event</b><br />
</td><br />
<td> Event identifier<br />
</td><br />
<td><br />
<p><code> String:<br />
playhead,pause,complete,<br />
adStop</code><br />
</p><br />
</td><br />
<td><syntaxhighlight lang="swift">"event":"playhead"</syntaxhighlight><br />
</td></tr><br />
<tr><br />
<td> <b>type</b><br />
</td><br />
<td> Determines the metadata object <br />
that should be used for crediting.<br />
</td><br />
<td><br />
<p><code> String:<br /><br />
content,<br />
ad,<br />
static</code><br />
</p><br />
</td><br />
<td><syntaxhighlight lang="swift">"type":"content"</syntaxhighlight><br />
</td></tr><br />
<tr><br />
<td> <b>metadata</b><br />
</td><br />
<td> Object that holds metadata values of specific types<br /><br />
<p><span style="color:blue"> Detailed in tables below</span><br />
</p><br />
</td><br />
<td> <code>Object</code><br />
</td><br />
<td><syntaxhighlight lang="swift"><br />
"metadata":{ <br />
"content": <content metadata object>,<br />
"ad": <ad metadata object>,<br />
"static": <static metadata object><br />
},<br />
</syntaxhighlight><br />
</td></tr><br />
<tr><br />
<td> <b>playheadPosition</b><br />
</td><br />
<td> Playhead value in seconds or Unix timestamp<br />
</td><br />
<td> <code>String</code><br />
</td><br />
<td><br />
<p>Position value is Unix timestamp:<br />
<code><br />
"playheadPosition":"1543437655"</code><br />
</p><p>Position value is playhead:<br />
<code><br />
"playheadPosition":"10"</code><br />
</p><br />
</td></tr><br />
</table><br />
=== Content Metadata ===<br />
Content metadata sent for every playheadposition update.<br />
<table><br />
<tr><br />
<th> Keys </th><br />
<th> Description </th><br />
<th> Example </th><br />
<th> Required<br />
</th></tr><br />
<tr><br />
<td>'''assetName''' </td><br />
<td> name of program (100 character limit) </td><br />
<td> <code>"MyTest789"</code> </td><br />
<td> Yes<br />
</td></tr><br />
<tr><br />
<td> '''assetid''' </td><br />
<td> unique ID assigned to asset </td><br />
<td> custom </td><br />
<td> Yes<br />
</td></tr><br />
<tr><br />
<td> '''length''' </td><br />
<td> length of content in seconds </td><br />
<td> <code>seconds</code> (86400 for live stream) </td><br />
<td> Yes<br />
</td></tr><br />
<tr><br />
<td> '''program''' </td><br />
<td>name of program (100 character limit) </td><br />
<td> custom </td><br />
<td> Yes<br />
</td></tr><br />
<tr><br />
<td> '''segB''' </td><br />
<td> custom segment B +</td><br />
<td> custom </td><br />
<td><br />
</td></tr><br />
<tr><br />
<td> '''segC''' </td><br />
<td> custom segment C +</td><br />
<td> custom </td><br />
<td><br />
</td></tr><br />
<tr><br />
<td> '''title''' </td><br />
<td>name of program (100 character limit) </td><br />
<td> custom </td><br />
<td> Yes<br />
</td></tr><br />
<tr><br />
<td>'''type'''</td><br />
<td><code>'content', 'ad', 'static'</code></td><br />
<td> <code> 'content'</code> </td><br />
<td> Yes<br />
</td></tr><br />
<tr><br />
<td>'''section''' </td><br />
<td> Unique Value assigned to page/site section </td><br />
<td> HomePage </td><br />
<td> Yes<br />
</td></tr><br />
<tr><br />
<td> '''airdate''' </td><br />
<td> the airdate in the linear TV ++</td><br />
<td> YYYYMMDD HH24:MI:SS </td><br />
<td> Yes<br />
</td></tr><br />
<tr><br />
<td> '''isfullepisode''' </td><br />
<td> full episode flag </td><br />
<td> <code>"y"</code>- full episode, <code>"n"</code>- non full episode </td><br />
<td> Yes<br />
</td></tr><br />
<tr><br />
<td> '''crossId1''' </td><br />
<td> Gracenote ID or Enterprise ID </td><br />
<td> custom </td><br />
<td> Yes<br />
</td></tr><br />
<tr><br />
<td> '''crossId2 '''</td><br />
<td> content originator or network (only required for distributors) </td><br />
<td> custom </td><br />
<td><br />
</td></tr><br />
<tr><br />
<td> <b>adloadtype</b><br />
</td><br />
<td> linear vs dynamic ad model<br />
</td><br />
<td> 1=Linear<br />
2=Dynamic Ads<br />
</td><br />
<td>custom<br />
</td></tr><br />
</table><br />
+ '''Custom segments''' (segB and segC) can be used to aggregate video and/or static content within a single Brand to receive more granular reports within a brand.<br><br />
++ Acceptable '''Air Date''' Formats:<br />
<syntaxhighlight lang="json"><br />
YYYYMMDD HH24:MI:SS<br />
YYYYMMDDHH24MISS<br />
YYYY-MM-DDTHH:MI:SS<br />
YYYY-MM-DDHH:MI:SS <br />
YYYYMMDDHH:MI:SS<br />
MM-DD-YYYY<br />
YYYYMMDD HH:MI:SS<br />
</syntaxhighlight><br />
<br><br />
For USA all times should be EST, for all other countries Local Time.<br />
Below is a sample event for DCR. If no ad or static values, these can be left as blank/null.<br />
<syntaxhighlight lang="json"><br />
{ <br />
"event": "playhead",<br />
"type": "content",<br />
"metadata": { <br />
"content":{<br />
"assetName":"Big Buck Bunny",<br />
"assetid":"B66473",<br />
"length":"3600",<br />
"program":"MyProgram",<br />
"segB":"CustomSegmentValueB",<br />
"segC":"segmentC",<br />
"title":"S2,E3",<br />
"type":"content",<br />
"section":"cloudApi_app",<br />
"airdate":"20180120 10:00:00",<br />
"isfullepisode":"y",<br />
"crossId1":"Standard Episode ID",<br />
"crossId2" :"Content Originator",<br />
"adloadtype":"2"},<br />
"ad": {},<br />
"static": {}<br />
},<br />
"playheadPosition": "",<br />
}<br />
</syntaxhighlight><br />
<br />
|DTVR=<br />
=== Digital TV Ratings info ===<br />
<table><br />
<tr><br />
<th> Parameter<br />
</th><br />
<th> <b>Description</b><br />
</th><br />
<th> <b>Supported values</b><br />
</th><br />
<th> <b>Example</b><br />
</th></tr><br />
<tr><br />
<td> <b>event</b><br />
</td><br />
<td> Event identifier<br />
</td><br />
<td><br />
<p><code> String:<br />
playhead,pause,complete,<br />
adStop</code><br />
</p><br />
</td><br />
<td><syntaxhighlight lang="swift">"event":"playhead"</syntaxhighlight><br />
</td></tr><br />
<tr><br />
<td> <b>type</b><br />
</td><br />
<td> Determines the metadata object that should be used for crediting.<br />
</td><br />
<td><br />
<p><code> String:<br /><br />
content,<br />
ad,<br />
static</code><br />
</p><br />
</td><br />
<td><syntaxhighlight lang="swift">"type":"content"</syntaxhighlight><br />
</td></tr><br />
<tr><br />
<td> <b>adModel</b><br />
</td><br />
<td> linear vs dynamic ad model<br />
</td><br />
<td> 1=Linear<br />
2=Dynamic Ads<br />
</td><br />
<td>custom<br />
</td></tr><br />
<tr><br />
<td> <b>channelName</b><br />
</td><br />
<td> Any string representing the.channel/stream<br />
</td><br />
<td> <code>Object</code><br />
</td><br />
<td>custom<br />
</td></tr><br />
<tr><br />
<td> <b>playheadPosition</b><br />
</td><br />
<td> Playhead value in seconds or Unix timestamp<br />
</td><br />
<td> <code>String</code><br />
</td><br />
<td><br />
<p>Position value is Unix timestamp:<br />
<code><br />
"playheadPosition":"1543437655"</code><br />
</p><p>Position value is playhead:<br />
<code><br />
"playheadPosition":"10"</code><br />
</p><br />
</td></tr><br />
<tr><br />
<td> <b>id3Data</b><br />
</td><br />
<td> Nielsen ID3 payload string<br />
</td><br />
<td> <code>Object</code><br />
</td><br />
<td><br />
<p><code><br />
id3Data: www.nielsen.com/065H2g6E7ZyQ5UdmMAbbpg<br />
==/_EMc37zfVgq_8KB7baUYfg==/ADQCAmgV1Xyvnynyg60<br />
kZO_Ejkcn2KLSrTzyJpZZ-QeRn8GpMGTWI7-HrEKzghxyzC<br />
yBEoIDv2kA2g1QJmeYOl5GnwfrLDVK2bNLTbQxr1z9VBfxa<br />
hBcQP5tqbjhyMzdVqrMKuvvJO1jhtSXa9AroChb11ZUnG1W<br />
VJx2O4M=/33648/22847/00</code><br />
</p><br />
<tr><br />
<br />
</td></tr><br />
</table><br />
Below is a sample event for DTVR. If no ad or static values, these can be left as blank/null.<br />
<syntaxhighlight lang="json"><br />
{ <br />
"event": "playhead",<br />
"type": "content",<br />
"metadata": { <br />
"content":{<br />
"adModel":"1",<br />
"channelname":"channel1"<br />
},<br />
"ad": {},<br />
"static": {}<br />
},<br />
"playheadPosition": "",<br />
"id3Data": "www.nielsen.com/065H2g6E7ZyQ5UdmMAbbpg==/_<br />
EMc37zfVgq_8KB7baUYfg==/ADQCAmgV1Xyvnynyg60kZO_Ejkcn<br />
2KLSrTzyJpZZ-QeRn8GpMGTWI7-HrEKzghxyzCyBEoIDv2kA2g1Q<br />
JmeYOl5GnwfrLDVK2bNLTbQxr1z9VBfxahBcQP5tqbjhyMzdVqrMK<br />
uvvJO1jhtSXa9AroChb11ZUnG1WVJx2O4M=/33648/22847/00"<br />
}<br />
</syntaxhighlight><br />
|DCRDTVR=<br />
=== Applies to DCR and DTVR ===<br />
<table><br />
<tr><br />
<th> Parameter<br />
</th><br />
<th> <b>Description</b><br />
</th><br />
<th> <b>Supported values</b><br />
</th><br />
<th> <b>Example</b><br />
</th></tr><br />
<tr><br />
<td> <b>event</b><br />
</td><br />
<td> Event identifier<br />
</td><br />
<td><br />
<p><code> String:<br />
playhead,<br />
pause,<br />
complete,<br />
adStop</code><br />
</p><br />
</td><br />
<td><syntaxhighlight lang="swift">"event":"playhead"</syntaxhighlight><br />
</td></tr><br />
<tr><br />
<td> <b>type</b><br />
</td><br />
<td> Determines the metadata object that should be used for crediting.<br />
</td><br />
<td><br />
<p><code> String:<br /><br />
content,<br />
ad,<br />
static</code><br />
</p><br />
</td><br />
<td><syntaxhighlight lang="swift">"type":"content"</syntaxhighlight><br />
</td></tr><br />
<tr><br />
<td> <b>metadata</b><br />
</td><br />
<td> Object that holds metadata values of specific types<br /><br />
<p><span style="color:blue"> Detailed in tables below</span><br />
</p><br />
</td><br />
<td> <code>Object</code><br />
</td><br />
<td><syntaxhighlight lang="swift"><br />
"metadata":{ <br />
"content": <content metadata object>,<br />
"ad": <ad metadata object>,<br />
"static": <static metadata object><br />
},<br />
</syntaxhighlight><br />
</td></tr><br />
<tr><br />
<td> <b>playheadPosition</b><br />
</td><br />
<td> Playhead value in seconds or Unix timestamp<br />
</td><br />
<td> <code>String</code><br />
</td><br />
<td><br />
<p>Position value is Unix timestamp:<br />
<code><br />
"playheadPosition":"1543437655"</code><br />
</p><p>Position value is playhead:<br />
<code><br />
"playheadPosition":"10"</code><br />
</p><br />
</td></tr><br />
<tr><br />
<td> <b>id3Data</b><br />
</td><br />
<td> Nielsen ID3 payload string<br />
</td><br />
<td> <code>Object</code><br />
</td><br />
<td><br />
<p><code><br />
id3Data: www.nielsen.com/065H2g6E7ZyQ5UdmMAbbpg<br />
==/_EMc37zfVgq_8KB7baUYfg==/ADQCAmgV1Xyvnynyg60<br />
kZO_Ejkcn2KLSrTzyJpZZ-QeRn8GpMGTWI7-HrEKzghxyzC<br />
yBEoIDv2kA2g1QJmeYOl5GnwfrLDVK2bNLTbQxr1z9VBfxa<br />
hBcQP5tqbjhyMzdVqrMKuvvJO1jhtSXa9AroChb11ZUnG1W<br />
VJx2O4M=/33648/22847/00</code><br />
</p><br />
</td></tr><br />
<tr><br />
<td> <b>ottData</b><br />
</td><br />
<td> Object that holds ott information<br />
</td><br />
<td> <code>Object</code><br />
</td><br />
<td><syntaxhighlight lang="swift"><br />
ottData:{<br />
ottStatus:1,<br />
ottType:casting,<br />
ottDevice:chromecast,<br />
ottDeviceName:Google ChromeCast,<br />
ottDeviceID:1234,<br />
ottDeviceModel:ChromeCast,<br />
ottDeviceVersion:1.0.0<br />
}<br />
</syntaxhighlight><br />
</td></tr><br />
</table><br />
=== Content Metadata ===<br />
Content metadata sent for every playheadposition update.<br />
<table><br />
<tr><br />
<th> Keys </th><br />
<th> Description </th><br />
<th> Example </th><br />
<th> Required<br />
</th></tr><br />
<tr><br />
<td> '''assetid''' </td><br />
<td> unique asset ID for DCR; can be set to "0" for DTVR </td><br />
<td> <code>EP732481</code> </td><br />
<td> Yes<br />
</td></tr><br />
<tr><br />
<td> '''length''' </td><br />
<td> length of content in seconds </td><br />
<td> <code>seconds</code> (86400 for live stream) </td><br />
<td> Yes<br />
</td></tr><br />
<tr><br />
<td>'''type'''</td><br />
<td><code>'content', 'ad', 'static'</code></td><br />
<td> <code> 'content'</code> </td><br />
<td> Yes<br />
</td></tr><br />
<tr><br />
<td> <b>adModel</b><br />
</td><br />
<td> linear vs dynamic ad model<br />
</td><br />
<td> 1=Linear<br />
2=Dynamic Ads<br />
</td><br />
<td>custom<br />
</td></tr><br />
<tr><br />
<td> <b>adloadtype</b><br />
</td><br />
<td> DCR Ad Model<br />
</td><br />
<td> 1=Linear<br />
2=Dynamic Ads<br />
</td><br />
<td>custom<br />
</td></tr><br />
</table><br />
+ '''Custom segments''' (segB and segC) can be used to aggregate video and/or static content within a single Brand to receive more granular reports within a brand.<br><br />
++ Acceptable '''Air Date''' Formats:<br />
<syntaxhighlight lang="json"><br />
YYYYMMDD HH24:MI:SS<br />
YYYYMMDDHH24MISS<br />
YYYY-MM-DDTHH:MI:SS<br />
YYYY-MM-DDHH:MI:SS <br />
YYYYMMDDHH:MI:SS<br />
MM-DD-YYYY<br />
YYYYMMDD HH:MI:SS<br />
</syntaxhighlight><br />
<br><br />
Below is a sample event for DCR/DTVR joint integration. If no ad or static values, these can be left as blank/null.<br />
<syntaxhighlight lang="json"><br />
{ <br />
"event": "playhead",<br />
"type": "content",<br />
"metadata": { <br />
"content":{<br />
"type":"content",<br />
"length":"86400",<br />
"adModel":"1",<br />
"adloadtype":"1"},<br />
"ad": {},<br />
"static": {}<br />
},<br />
"playheadPosition": "",<br />
"id3Data": "www.nielsen.com/065H2g6E7ZyQ5UdmMAbbpg==/_<br />
EMc37zfVgq_8KB7baUYfg==/ADQCAmgV1Xyvnynyg60kZO_Ejkcn<br />
2KLSrTzyJpZZ-QeRn8GpMGTWI7-HrEKzghxyzCyBEoIDv2kA2g1Q<br />
JmeYOl5GnwfrLDVK2bNLTbQxr1z9VBfxahBcQP5tqbjhyMzdVqrMK<br />
uvvJO1jhtSXa9AroChb11ZUnG1WVJx2O4M=/33648/22847/00"<br />
}<br />
</syntaxhighlight><br />
}}<br />
<br />
=== Ad Metadata ===<br />
The ad metadata (if applicable) should be passed for each individual ad, if ads are available during or before the stream begins.<br />
<br/><br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Example<br />
|-<br />
| assetid || unique ID assigned to ad || custom <br>(no [[Special Characters]]) || <code>'AD1234'</code><br />
|-<br />
| title || unique name assigned to ad || custom ||<code>'ADtitle'</code><br />
|-<br />
|adldx || Ad Index (*See Note below*) || custom || <code> "66478364" </code><br />
|-<br />
| type || type of ad || 'preroll', 'midroll', or 'postroll' || <code>'preroll'</code><br />
|-<br />
|length || length of ad || In Seconds ||<code> '20' </code><br />
|}<br />
=== Ad Metadata Sample ===<br />
<syntaxhighlight lang="json"><br />
{<br />
"ad": {<br />
"assetid":"AD12345",<br />
"title":"ADTestTitle",<br />
"adldx":"1",<br />
"type":"preroll",<br />
"length":"20"<br />
},<br />
}<br />
</syntaxhighlight><br />
=== Managing Ads ===<br />
If there is an Ad block within the playing content (such as a midroll) you need to:<br />
* Reset the playhead position to 0 for each ad.<br />
* Call the '''adStop''' event at the end of each ad or increment the adldx<br />
<br />
The Simplified SDK will can automatically detect the change from ad to content, or even ad to ad if the assetID changes; however, there could be situations where the same ad is played back to back. You can either increment/change the '''adldx value''', and/or call adStop at the end of each Ad.<br />
<br />
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 Nielsen Simplified API will support a new parameter for ad metadata: '''adIdx.''' This parameter is just 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.<br />
<br />
<syntaxhighlight lang="swift"><br />
// Example of passing both values<br />
self.data.updateValue("adStop", forKey: "event")<br />
self.data.updateValue("223", forKey: "adldx")<br />
self.nielsenEventTracker.trackEvent(data)<br />
</syntaxhighlight><br />
<br />
<br />
{{Template:Browser_Privacy_and_Opt-Out}}<br />
<br />
== Going Live ==<br />
After the integration has been certified, users will need to make a couple of updates to the initialization call to ensure that player will be measured properly.<br />
Disable debug logging by deleting {nol_sdkDebug: 'DEBUG'} from initialization call.<br />
Example Production Initialization Call<br />
<br />
<syntaxhighlight lang="javascript"><br />
var nSdkInstance = NOLBUNDLE.nlsQ("XXXXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXX", "nlsnInstance", { outout: "false"});<br />
</syntaxhighlight><br />
<br />
== Sample Code ==<br />
This code is for example purposes only to demonstrate a very simple video implementation, with a single preroll and single content block.<br />
<syntaxhighlight lang=javascript><br />
<script ><br />
// Setup videojs sample player<br />
var nielsenid = document.getElementById("my-sdkplayer");<br />
var coolPlayer = videojs('my-sdkplayer', {<br />
controls: true,<br />
autoplay: true,<br />
preload: 'auto',<br />
});<br />
var prevPos = null;<br />
<br />
coolPlayer.preroll({<br />
src: {<br />
src: "assets/CleaningCrew.mp4",<br />
type: "video/mp4"<br />
},<br />
href: "http://videojs.com",<br />
adsOptions: {<br />
debug: true<br />
}<br />
});<br />
<br />
videojs('my-sdkplayer').ready(function() {<br />
// EXAMPLE: Start playing the video.<br />
<br />
coolPlayer.play();<br />
var obj = {<br />
event: 'playhead', // playhead,pause,complete,adstop<br />
type: 'content',<br />
playheadPosition: '0',<br />
metadata: {<br />
content: {<br />
type: 'content',<br />
assetid: 'VID98-2B88',<br />
program: 'Put a programname here ',<br />
section: 'section name here'<br />
title: 'S01E02',<br />
length: '180',<br />
airdate: '20190101 10:01:00',<br />
isfullepisode: 'y',<br />
crossId1: 'Standard Episode ID',<br />
crossID2: 'Content Originator',<br />
adloadtype: '2'<br />
},<br />
ad: {},<br />
static: {}<br />
}<br />
};<br />
<br />
coolPlayer.on('timeupdate', function() {<br />
var getcurrentTime = coolPlayer.currentTime();<br />
var intPlayedTime = parseInt(getcurrentTime, 10);<br />
if (intPlayedTime > 0 && intPlayedTime !== prevPos) {<br />
// check for playhead change so it's only called once per second <br />
console.log('PLAYHEAD EVENT **************************************************************');<br />
obj.event = 'playhead';<br />
obj.type = 'content';<br />
obj.metadata.ad = {};<br />
prevPos = intPlayedTime;<br />
obj.playheadPosition = Math.round(getcurrentTime);<br />
nSdkInstance.trackEvent(obj);<br />
}<br />
});<br />
<br />
this.on('adstart', function() {<br />
console.log('ADSTART EVENT **************************************************************');<br />
obj.type = 'ad';<br />
obj.metadata.ad = {<br />
type: 'preroll',<br />
assetid: 'AD9455',<br />
adldx: '1',<br />
program: 'Cant Skip Campaign',<br />
title: 'Cleaning Crew',<br />
length: '36',<br />
};<br />
nSdkInstance.trackEvent(obj);<br />
});<br />
<br />
this.on('contentresumed', function() {<br />
console.log('CONTENT RESUMED EVENT **************************************************************');<br />
obj.type = 'content';<br />
obj.event = 'playhead';<br />
obj.metadata.ad = {};<br />
nSdkInstance.trackEvent(obj);<br />
});<br />
<br />
this.on('pause', function() {<br />
console.log('PAUSE EVENT **************************************************************');<br />
obj.event = 'pause';<br />
obj.playheadPosition = Math.round(coolPlayer.currentTime());<br />
nSdkInstance.trackEvent(obj);<br />
});<br />
<br />
this.on('adtimeupdate', function() {<br />
var getcurrentTime = coolPlayer.currentTime();<br />
var intPlayedTime = parseInt(getcurrentTime, 10);<br />
if (intPlayedTime > 0 && intPlayedTime !== prevPos) {<br />
// check for playhead change so it's only called once per second <br />
console.log('AD PLAYHEAD EVENT **************************************************************');<br />
console.log(Math.round(getcurrentTime));<br />
prevPos = intPlayedTime;<br />
obj.playheadPosition = Math.round(getcurrentTime);<br />
nSdkInstance.trackEvent(obj);<br />
};<br />
});<br />
<br />
this.on('adend', function() {<br />
console.log('AD STOP EVENT **************************************************************');<br />
obj.event = 'adstop';<br />
nSdkInstance.trackEvent(obj);<br />
});<br />
<br />
this.on('ended', function() {<br />
console.log('COMPLETE EVENT **************************************************************');<br />
obj.event = 'complete';<br />
nSdkInstance.trackEvent(obj);<br />
});<br />
});<br />
<br />
</script><br />
</syntaxhighlight></div>AlexGutierrezhttps://engineeringportal.nielsen.com//w/index.php?title=DCR_Video_%26_Static_Cloud_API&diff=3445DCR Video & Static Cloud API2019-02-15T21:13:44Z<p>AlexGutierrez: </p>
<hr />
<div>{{Breadcrumb|}} {{Breadcrumb|Digital}} {{Breadcrumb|DCR & DTVR}} {{CurrentBreadcrumb}}<br />
[[Category:Digital]]<br />
<br />
This guide shows you how to integrate the Nielsen Cloud API to enable Digital Content Ratings (DCR) measurement on your over-the-top (OTT) Apps.<br />
*For Roku Apps, please see [[DCR Video & Static Roku Cloud API]]<br />
*For Mobile Apps, please see [[DCR Video & Static Mobile Cloud API]]<br />
<br />
==Prerequisites==<br />
To get started, you will need a Nielsen App ID. The App ID is a unique ID assigned to your app. This will be provided to you upon starting the integration.<br />
<br />
<syntaxhighlight lang="javascript">XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX</syntaxhighlight><br />
<br />
==Integration==<br />
We will cover the steps for constructing the Cloud API Calls.<br />
<br />
===Request Overview===<br />
<br />
====URL Structure====<br />
<br />
The Cloud API Calls are HTTP GET Requests with the URL structure:<br />
<br />
<syntaxhighlight lang="javascript">[endpoint]/[appid]/[sessionID]/a?b=[payload]</syntaxhighlight><br />
<br />
The URL includes the following components:<br />
<br />
*<code>[endpoint]</code>: location of data collection environment<br />
*<code>[appid]</code>: provided App ID<br />
*<code>[sessionID]</code>: unique value for each user session<br />
*<code>[payload]</code>: metadata and events<br />
<br />
====Endpoint====<br />
<br />
There are endpoints for testing and production:<br />
<br />
*Testing: <code>https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/</code><br />
*Production: <code>https://cloudapi.imrworldwide.com/nmapi/v2/</code><br />
<br />
During testing, all calls should be pointed to the testing endpoint. We will review the update to the production endpoint during the Go Live section of this guide.<br />
<br />
====URL Example====<br />
As you move through the integration steps, you can reference the below URL structure with the expanded payload:<br />
<br />
<syntaxhighlight lang="javascript"><br />
https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=<br />
{<br />
"devInfo": [deviceInfo],<br />
"metadata": {<br />
"static": [static_metadata],<br />
"content": [content_metadata],<br />
"ad": [ad metadata]<br />
},<br />
"event": [event],<br />
"position": [playhead_position],<br />
"type": [asset type],<br />
"utc": [Unix time in ms]<br />
}<br />
</syntaxhighlight><br />
<br />
===Create Session ID===<br />
A unique Session ID must be created upon app launch and provided in the URL. This will allow measurement to occur for the entire duration that a user is within the app.<br />
<br />
A Session ID needs to be completely unique so it is recommended to use a version 4 UUID or another method of your choosing to guarantee there are no repeats.<br />
<br />
Upon exiting the app, the session will need to be terminated using the delete event. Sessions will automatically expire after 30 minutes of cloud inactivity.<br />
<br />
===Define URL Structure===<br />
Define the URL structure using your provided <code>[appid]</code> and a unique <code>[sessionID]</code>.<br />
<br />
<syntaxhighlight lang="javascript">https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=[payload]</syntaxhighlight><br />
<br />
===Configure Payload===<br />
<br />
All Cloud API requests must contain the following payload data:<br />
<br />
*''devInfo'': device and app info<br />
*''metadata'': asset metadata<br />
*''event metadata'': type of event<br />
<br />
The payload can be passed through key-values using the Nielsen reserved keys. The specific keys and descriptions are highlighted in the tables included in this section.<br />
<br />
'''Payload Example'''<br />
<br />
The example below should be referenced when following the steps for configuring the request payload.<br />
<br />
<syntaxhighlight lang="javascript"><br />
// 3.1 Configure Payload: devInfo <br />
payload = {<br />
"devInfo": {<br />
"devId": "AD-ID", <br />
"apn": "AppName",<br />
"apv": "1.0",<br />
"uoo": "false"<br />
},<br />
<br />
// 3.2 Configure Payload: metadata<br />
"metadata": {<br />
"static": {}, // object for measuring static content<br />
"content": { // object for measuring video content<br />
"type": "content", // "content" for video<br />
"assetid": "VIDEO-ID123", // unique ID for video<br />
"isfullepisode": "y", // full episode flag<br />
"program": "Program Name", // program name<br />
"title": "Episode Title S3 - EP1", // episode name<br />
"length": "1800", // content duration in seconds<br />
"segB": "Custom Segment B", // custom segment<br />
"segC": "Custom Segment C", // custom segment<br />
"crossId1": "Standard Episode ID", // episode ID<br />
"crossId2": "Content Originator ID", // content orginator (required for distributors)<br />
"airdate": "20161013 20:00:00", // airdate<br />
"adloadtype": "2" //ad load flag<br />
"hasAds": "1", // content contains ads = 1 / no ads = 0<br />
"progen": "CV" // program genre abbreviation<br />
},<br />
"ad": {<br />
"type": "preroll", // type of ad<br />
"assetid": "AD-ID123" // unique ID for ad<br />
}<br />
},<br />
<br />
// 3.3 Configure Payload: events<br />
"event": "playhead", //event name<br />
"position": "300", // position in seconds<br />
"type": "content", //"content" or "ad"<br />
"utc": "1456448742000" //unix timestamp in milliseconds <br />
}<br />
</syntaxhighlight><br />
<br />
=====Configure Payload: devInfo=====<br />
An object <code>"devInfo"</code> will need to be created to capture App and Device information.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| devId || unique ID to identify user (e.g. Advertising ID, Roku Device ID) || custom || Yes<br />
|-<br />
| apn || app name || custom || Yes<br />
|-<br />
| apv || app build version || custom || Yes<br />
|-<br />
| uoo || device opt-out status || <code>"true"</code> or <code>"false"</code> || Yes<br />
|-<br />
|}<br />
<br />
'''Example devInfo Object'''<br />
<syntaxhighlight lang="javascript"><br />
// create devInfo object<br />
"devInfo": {<br />
"devId": "AD-ID",<br />
"apn": "AppName",<br />
"apv": "1.0",<br />
"uoo": "false"<br />
},<br />
</syntaxhighlight><br />
<br />
==== 3.2 Configure Payload: metadata ====<br />
Asset metadata can be passed through <code>"metadata"</code>. There are two asset types: <code>"content"</code> for video and <code>"ad"</code> for ads. The metadata received for each asset is used for classification and reporting.<br />
<br />
You will need to set up <code>"metadata"</code> objects for <code>"content"</code> and <code>"ad"</code> with the required Nielsen keys as shown in the sample code below.<br />
<br />
===== Content Metadata =====<br />
Content metadata should remain constant throughout the entirety of an episode/clip including when ads play.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| type || type of asset || <code>"content"</code> || Yes<br />
|-<br />
| assetid || unique ID assigned to asset || custom || Yes<br />
|-<br />
| program ||name of program (25 character limit) || custom || Yes<br />
|-<br />
| title ||name of program (25 character limit) || custom || Yes<br />
|-<br />
| length || length of content in seconds || <code>seconds</code> (0 for live stream) || Yes<br />
|-<br />
| segB || custom segment B || custom || <br />
|-<br />
| segC || custom segment C || custom || <br />
|-<br />
| airdate || the airdate in the linear TV || YYYYMMDD HH24:MI:SS || Yes<br />
|-<br />
| isfullepisode || full episode flag || <code>"y"</code>- full episode, <code>"n"</code>- non full episode || Yes<br />
|-<br />
| crossId1 || standard episode ID || custom || Yes<br />
|-<br />
| crossId2 || content originator (only required for distributors) || Nielsen ||<br />
|-<br />
| adloadtype || type of ad load:<br />
<code>"1"</code> Linear – matches TV ad load<br />
<br />
<code>"2"</code> Dynamic – Dynamic Ad Insertion (DAI)<br />
|| <code>"2"</code> - DCR measures content with dynamic ads || Yes<br />
|-<br />
| hasAds || ads indicator<br />
<code>"1"</code>: ads included<br />
<br />
<code>"0"</code>: ads not included<br />
|| <code>"1"</code> or <code>"0"</code> || Yes<br />
|-<br />
| progen || Genre (required only for non-TV originated content). See [[DCR OTT Genre List]] for accepted values || <code>"CV"</code> for Comedy Variety || Required for non-TV originated content<br />
|}<br />
<br />
<br />
'''Example Content Object'''<br />
<syntaxhighlight lang='json'>// create content object<br />
"content": {<br />
"type": "content",<br />
"assetid": "VIDEO-ID123",<br />
"isfullepisode": "y",<br />
"program": "Program Name",<br />
"title": "Episode Title S3 - EP1",<br />
"length": "1800",<br />
"segB": "Custom Segment B",<br />
"segC": "Custom Segment C",<br />
"crossId1": "Standard Episode ID",<br />
"crossId2": "Content Originator ID",<br />
"airdate": "20161013 20:00:00",<br />
"adloadtype": "2",<br />
"hasAds": "1", <br />
"progen": "CV"<br />
}</syntaxhighlight><br />
<br />
===== Ad Metadata =====<br />
The ad metadata should be passed for each individual ad.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| type || type of ad || <code>"preroll"</code>, <code>"midroll"</code>, or <code>"postroll"</code> || Yes<br />
|-<br />
| assetid || unique ID assigned to ad || custom || Yes<br />
|}<br />
<br />
===== Example Ad Object =====<br />
<syntaxhighlight lang="javascript"><br />
// create ad object<br />
"ad": {<br />
"type": "preroll",<br />
"assetid": "AD-ID123"<br />
}<br />
</syntaxhighlight><br />
<br />
=== Configure Payload: Events ===<br />
<br />
The last part of the payload is for enabling events so content is measured correctly when viewed. The events and required parameters are included below.<br />
<br />
==== Event Types ====<br />
<br />
The available events are:<br />
{| class="wikitable"<br />
|-<br />
! Event !! Description<br />
|-<br />
| <code>"playhead"</code> || Playhead position in seconds. Must be passed as a whole number every 10 seconds. The final playhead position should also be sent before an asset has changed to properly capture full duration. Playhead is used to handle pause and scrubbing. When content is paused, stop passing playhead position.<br />
|-<br />
| <code>"complete"</code> || Complete event must be sent when the content has completed full playback or if a user has initiated a Stop. Before calling the complete event, a final playhead update with the final position is required to be sent to receive full duration credit. Complete does not need to be called if a user transitions videos (e.g. channel change or selecting a new VOD asset) before the video completes.<br />
|-<br />
| <code>"delete"</code> || The delete event should be sent when the application is exited and the viewing session is completed. After 30 minutes of inactivity, the session will expire automatically. All creditable duration will be summarized for all asset types when delete occurs (content and ads). Note that the delete event may not be feasible on certain platforms that do not offer the necessary callbacks to trigger this event.<br />
|}<br />
<br />
===== Event Parameters =====<br />
<br />
The following parameters need to be passed when calling events:<br />
<br />
{| class="wikitable"<br />
|-<br />
! Parameter !! Description !! Value !! Required<br />
|-<br />
| <code>"event"</code> || event type || <code>"playhead"</code>, <code>"complete"</code>, or <code>"delete"</code> || Yes<br />
|-<br />
| <code>"position"</code> || playhead position in seconds or Unix time in seconds || <code>"300"</code> || Yes<br />
|-<br />
| <code>"type"</code> || asset type || <code>"content"</code>, <code>"ad"</code> || Yes<br />
|-<br />
| <code>"utc"</code> || Unix timestamp in milliseconds. Must be passed every 10 seconds. || <code>"1472760000000"</code> || Yes<br />
|}<br />
<br />
===== Example Event =====<br />
You can call events by passing values in the required parameters:<br />
<br />
<syntaxhighlight lang="javascript"><br />
"devInfo": [deviceInfo],<br />
"metadata": {<br />
"static": [static metadata],<br />
"content": [content metadata],<br />
"ad": [ad metadata]<br />
},<br />
// Event Parameters<br />
"event": [event], // event name<br />
"position": [playheadPosition], //position in seconds<br />
"type": [asset type], // values are "content" or "ad"<br />
"utc": "1472760000000" //unix timestamp in milliseconds<br />
}<br />
</syntaxhighlight><br />
<br />
'''Note:''' The full payload including "devInfo" and "metadata" must be populated in each event request.<br />
<br />
===== Sample Event Lifecycle =====<br />
The sample event lifecycle can be used as a reference for identifying the order for calling events and values to pass.<br />
<br />
<syntaxhighlight lang="javascript"><br />
// Start of Session: session ID created when App is opened<br />
<br />
// Preroll<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
// Content<br />
Content Playhead {"event": "playhead", "position": "0", "type": "content", "utc": "1472760000000"} <br />
<br />
// Midroll<br />
Midroll Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
// Content resumes at 15 minutes<br />
Content Playhead {"event": "playhead", "position": "900", "type": "content", "utc": "1472760000000"} <br />
<br />
// Content completes at 30 minutes<br />
Complete {"event": "complete", "position": "1800", "type": "content", "utc": "1472760000000"} <br />
<br />
// Postroll<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
//End of Session: The delete event should be called when the App is exited. The values for position and type not required to be passed.<br />
Delete { "event": "delete", "position": "", "type": "", "utc": "1472760000000"} <br />
</syntaxhighlight><br />
<br />
<br />
'''Sample Event Lifecycle - Detailed Storyline'''<br />
This detailed event sequence provides additional insight for the correct events to call when handling certain playback scenarios.<br />
<syntaxhighlight lang='javascript'>// SESSION STARTS<br />
// Start of Session: session ID created when App is opened<br />
<br />
// PREROLL<br />
// Preroll Start - Start each Ad with a position of "0", resetting to '0' for each Ad, and Ad break.<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
// Preroll Stop - End each Ad with the final position of the Ad.<br />
Ad Playhead {"event": "playhead", "position": "15", "type": "ad", "utc": "1472761500000"}<br />
<br />
// CONTENT <br />
// Content Start - Start new content streams with a position of "0" incrementing the position every 10 seconds.<br />
Content Playhead {"event": "playhead", "position": "0", "type": "content", "utc": "1472761500000"} <br />
<br />
// Content Stop Before Ad Break - Send a playhead update including the current content positon before an Ad break.<br />
Content Playhead {"event": "playhead", "position": "299", "type": "content", "utc": "1472787400000"}<br />
<br />
// MIDROLL<br />
// Midroll Start - Start each Ad with a position of "0", resetting to '0' for each Ad, and Ad break.<br />
Midroll Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472787500000"} <br />
<br />
// Midroll Stop - End each Ad with the final position of the Ad.<br />
Ad Playhead {"event": "playhead", "position": "60", "type": "ad", "utc": "1472793500000"}<br />
<br />
// CONTENT<br />
// Content resumes at 5 minutes - Send playhead update with the current resumed position, and begin incrimenting the positon every 10 seconds.<br />
Content Playhead {"event": "playhead", "position": "300", "type": "content", "utc": "1472799500000"} <br />
<br />
// Content completes at 10:12 - Make sure to send in the playhead event with the final content position before sending the complete event.<br />
Final Content Playhead {"event": "playhead", "position": "612", "type": "content", "utc": "1472830700000"} <br />
<br />
Complete {"event": "complete", "position": "612", "type": "content", "utc": "1472830800000"} <br />
<br />
// POSTROLL<br />
// Postroll Start - Start each Ad with a position of "0", resetting to '0' for each Ad, and Ad break.<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472830900000"} <br />
<br />
// Postroll Stop - End each Ad with the final position of the Ad.<br />
Ad Playhead {"event": "playhead", "position": "45", "type": "ad", "utc": "1472835300000"}<br />
<br />
// SESSION ENDS<br />
<br />
//End of Session: The delete event should be called when the App is exited. The values for position and type not required to be passed.<br />
Delete { "event": "delete", "position": "", "type": "", "utc": "1472835400000"} </syntaxhighlight><br />
<br />
<br />
=====Handling Playhead=====<br />
Calling <code>"playhead"</code> is critical for accurate duration crediting. You can reference the below guidance to determine the correct playhead position to pass depending on the playback scenario.<br />
<br />
'''Playhead: General'''<br />
* Playhead position must start at 0 for each new asset, and be passed at least every 10 seconds.<br />
* Final postion must be sent at the end of content or an ad<br />
* Playheads should be sent in seconds only, not milliseconds<br />
<br />
'''Playhead: Ads'''<br />
* The final position must be sent when switching from content to ad, or ad to content.<br />
* Each ad playhead position should be 0 at ad start.<br />
* For Ad Pods, playhead must be called, and reset to 0 for each individual ad. <br />
* The last content position before an Ad should be sent before switching to Ads.<br />
* When content has resumed following an ad break, the playhead position update must continue where the previous content segment left off.<br />
<br />
'''Playhead: User Actions'''<br />
* Upon user scrubbing, the current position must be sent before a user scrubs, and the new position should be sent where the user lands, and begin sending in the 10 second updates thereafter.<br />
* On pause, send the current position and then discontinue sending playhead event updates.<br />
* If a user exits a stream early, the last current position must be sent in a playhead update to receive accurate duration.<br />
<br />
===== Interruption Scenarios =====<br />
<br />
As part of configuring events, you will need to handle all possible interruption scenarios such as:<br />
<br />
*Wi-Fi OFF / ON<br />
*App going Background / Foreground (Video players only, not for Audio players)<br />
*App Crash or Exit<br />
<br />
When playback is interrupted, the app needs to send delete immediately.<br />
<br />
Once playback resumes, a new session will need to be created with a unique session ID. All of the required metadata and events will need to be sent.<br />
<br />
'''Note:''' The session will automatically timeout after 30 minutes of inactivity.<br />
<br />
=== Example Request ===<br />
<br />
Now that we walked through the Cloud API integration steps, your requests should have the following components: Session ID, App ID, and Payload. You can reference the example below when your reviewing your integration.<br />
<br />
<syntaxhighlight lang="javascript"><br />
// 1. Create Session ID<br />
sessionID = dfc7dc6a-66a7-4705-9fba-adaaf7e3d5e0 // Example sessionID created using a UUID Generator<br />
<br />
// 2. Define URL Structure with App ID and Session ID<br />
sessionURL = https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=<br />
<br />
// 3. Configure Payload<br />
// 3.1 Configure Payload: devInfo <br />
payload = {<br />
"devInfo": {<br />
"devId": "AD-ID", <br />
"apn": "AppName",<br />
"apv": "1.0",<br />
"uoo": "false"<br />
},<br />
<br />
// 3.2 Configure Payload: metadata<br />
"metadata": {<br />
"static": {}, // object for measuring static content<br />
"content": { // object for measuring video content<br />
"type": "content", // "content" for video<br />
"assetid": "VIDEO-ID123", // unique ID for video<br />
"isfullepisode": "y", // full episode flag<br />
"program": "Program Name", // program name<br />
"title": "Episode Title S3 - EP1", // episode name<br />
"length": "1800", // content duration in seconds<br />
"segB": "Custom Segment B", // custom segment<br />
"segC": "Custom Segment C", // custom segment<br />
"crossId1": "Standard Episode ID", // episode ID<br />
"crossId2": "Content Originator ID", // content orginator (required for distributors)<br />
"airdate": "20161013 20:00:00", // airdate<br />
"adloadtype": "2", // ad load flag<br />
"hasAds": "1", // content contains ads = 1 / no ads = 0<br />
"progen": "CV" // program genre abbreviation<br />
},<br />
"ad": {<br />
"type": "preroll", // type of ad<br />
"assetid": "AD-ID123" // unique ID for ad<br />
}<br />
},<br />
<br />
// 3.3 Configure Payload: events<br />
"event": "playhead", //event name<br />
"position": "300", // position in seconds<br />
"type": "content", //"content" or "ad"<br />
"utc": "1456448742000" //unix timestamp in milliseconds <br />
}<br />
<br />
// Append payload to URL<br />
var image = new Image()<br />
image.onerror = function() {<br />
// wait and send again<br />
}<br />
(new Image).src = sessionURL+encodeURI(JSON.stringify(payload));<br />
</syntaxhighlight><br />
<br />
==== Enable Debug Logging ====<br />
<br />
Now that you have set up the Cloud API requests, you can enable debug logging to validate your integration. Enabling debug logging is required for Nielsen certification.<br />
<br />
==== GET Request ====<br />
<br />
Display GET Request to console using a name to identify each event (e.g. playhead).<br />
<br />
<syntaxhighlight lang="javascript"><br />
console.log("Event", image); <br />
</syntaxhighlight><br />
<br />
==== Payload ====<br />
<br />
Output payload to identify required metadata and events.<br />
<br />
<syntaxhighlight lang="javascript"><br />
console.log("Event Payload", payload); <br />
</syntaxhighlight><br />
<br />
==== HTTP Response Code ====<br />
<br />
Confirm request was completed by viewing HTTP response code.<br />
<br />
<syntaxhighlight lang="javascript"><br />
code = msg.GetResponseCode();<br />
console.log("Response Code", code); <br />
</syntaxhighlight><br />
<br />
You can reference the HTTP Response Code table when reviewing your requests:<br />
<br />
{| class="wikitable"<br />
|-<br />
! Status Code !! Status Text !! Description<br />
|-<br />
| <code>200</code> || OK || request received<br />
|-<br />
| <code>403</code> || Forbidden || invalid App ID<br />
|-<br />
| <code>404</code> || Not Found || JSON issue<br />
|}<br />
<br />
== Opt-Out ==<br />
Your app must provide a means for the user to Opt-Out, or Opt-In to Nielsen Measurement. This requirement can be fulfilled by checking the device OS for the user's setting of "Limit Ad Tracking" or similar option. If the device offers "Limit Ad Tracking" settings, you should set <code>uoo=true</code> or <code>uoo=false</code> depending on the user's privacy setting. Also, ensure that the <code>devId</code> is set to a blank value if the user elects to opt-out.<br />
<br />
If the device does not have OS-level "Do Not Track" settings, you can implement opt-out by creating an Opt-Out/Opt-In button, toggle switch, or slider within the app "Settings", or "About" section to allow the user selection.<br />
<br />
[[File:Nielsen Opt-Out.png|link=]]<br />
<br />
You will need to store the User Opt-Out (uoo) status, so that it can be retrieved and populated in the <code>"devInfo"</code> metadata which will be sent in every Cloud API event (playhead, complete, & delete). <br />
<br />
{| class="wikitable"<br />
|-<br />
! uoo Key !! Description !! Values<br />
|-<br />
| uoo || Device is Opted-In to Nielsen Measurement (Recommended Default Setting) || <code>"false"</code><br />
|-<br />
| uoo || Device is Opted-Out of Nielsen Measurement || <code>"true"</code><br />
|}<br />
<br />
===== devInfo Opt-In JSON Payload Example =====<br />
<br />
<syntaxhighlight lang="javascript"><br />
"devInfo": {<br />
"apn": "Cloud API Sample App",<br />
"apv": "1",<br />
"devId": "7be25cf9-8c40-5cc2-871e-19bf41940288",<br />
"uoo": "false"<br />
},<br />
</syntaxhighlight><br />
<br />
===== devInfo Opt-Out JSON Payload Example =====<br />
<br />
<syntaxhighlight lang="javascript"><br />
"devInfo": {<br />
"apn": "Cloud API Sample App",<br />
"apv": "1",<br />
"devId": "", //devId must be blank when a user elects to Opt-Out.<br />
"uoo": "true"<br />
},<br />
</syntaxhighlight><br />
<br />
===== Privacy Information Template To Include In Opt-Out Screen =====<br />
<br />
<br />
Additionally, all applications must display the Nielsen privacy policy within their application, typically in the Settings/About screen of your application. The Nielsen privacy policy text is listed below.<br />
<br />
<blockquote><br />
'''''ABOUT NIELSEN MEASUREMENT'''''<br />
<br />
Television and the way we watch it have come a long way since Nielsen began measuring TV audiences in 1950. Today, the ability to watch our favorite shows at any time and on multiple devices amplifies the need for exceptionally adept and flexible audience measurement capabilities.<br />
<br />
Consumers are changing with the times, and the same goes for us. As technology continues to evolve and media companies try new ways to attract viewers, understanding what consumers are watching — and what they're watching on — is more important than ever. Today, viewing video is a personal and mobile experience — anytime and anywhere. Our capabilities provide relevant metrics that are necessary to inform successful marketing and programming and drive continued growth. As a global information and measurement leader, we are committed to protecting the privacy and security of the data we collect, process and use. While our digital measurement products are not used to identify you in any way, they help us and our clients measure and analyze how consumers engage with media across online, mobile and emerging technologies, and offer insights into consumer behavior.<br />
<br />
'''''YOUR CHOICES'''''<br />
<br />
Nielsen believes that you should have a choice about whether to contribute to our research and insights. To opt out or opt in to Nielsen measurement, please toggle your "Limit Ad Tracking" (or similar setting) on your device, or make the appropriate selection within the application's settings. If you have this app on more than one device, you will need to opt out of this app on each device. To learn more about our digital measurement products and your choices in regard to them, please visit http://www.nielsen.com/digitalprivacy.<br />
<br />
'''''Disclosure Template'''''<br />
<br />
Additionally, you must update the App description information from the App Distribution Store with the Nielsen Measurement disclosure below:<br />
<br />
This app features Nielsen's proprietary measurement software which will allow you to contribute to market research, like Nielsen's TV Ratings. Please see http://www.nielsen.com/digitalprivacy for more information.<br />
</blockquote><br />
<br />
== Testing ==<br />
Before providing an app build to Nielsen for testing, it is important to run validation checks once you have enabled debug logging.<br />
<br />
=== Payload Validation ===<br />
<br />
Ensure that all of the required payload data is populating while testing several videos. The following areas are critical to measurement:<br />
*devInfo<br />
*Asset metadata for both content, and ads<br />
*Events<br />
*Opt-Out status<br />
<br />
=== Player Events ===<br />
Review event calls:<br />
<br />
==== playhead ====<br />
*Playhead position updates every 10 seconds starting at position '0' for each new asset.<br />
*Final playhead position is sent on content, or ad before switching between assets.<br />
*Content metadata remains constant throughout an episode, or clip play.<br />
*Ad metadata is populated appropriately for each individual ad.<br />
*Playhead position update resumes for content after an ad break, and resets to 0 for each individual ad.<br />
*For scrubbing, last current position should be sent while scrubbing occurs, and the new position should also be sent where the user scrubs to.<br />
*Exiting a stream early should execute the last current position in a playhead update to receive accurate duration.<br />
*Upon pause, the current position should be sent, and playhead updates should stop incrementing until resume play occurs.<br />
<br />
==== complete ====<br />
*Check that the complete event executes upon content complete after the final playhead update is sent<br />
*Do not execute the complete event for ads<br />
<br />
==== delete ====<br />
*Check to see that the delete event occurs upon app exit, if the platform has the necessary exit callback events.<br />
<br />
==== GET Request Format ====<br />
*Ensure that the event payloads are formatted in JSON<br />
*Check to see that each of the Cloud API GET requests are properly encoded<br />
<br />
==== HTTP Response ====<br />
*Make sure that each of the Cloud API Get requests are received by the Nielsen Cloud API properly through use of the HTTP Response Code outputs enabled in console.<br />
<br />
==== Opt-Out ====<br />
*Test the "uoo" key gets populated accurately for both Opt-In and Opt-Out selections by validating the Cloud API events called after the user Opt-Out/Opt-In selection.<br />
*Test that the devId field is populated with a blank value if a user has elected to Opt-Out. For example: "devId": "",<br />
*If the device supports "Limit Ad Tracking" or has device "Opt-Out" settings, test that uoo=true, and that devId is set to a blank value if enabled in the device settings.<br />
<br />
== Go Live ==<br />
After your integration has been certified, you will need to: Change Endpoint and Disable Logging.<br />
<br />
'''Change Endpoint:''' You will need to update to the production endpoint:<br />
<br />
*Testing: <code>https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/</code><br />
*Production: <code>https://cloudapi.imrworldwide.com/nmapi/v2/</code><br />
<br />
Your production URL structure should now be:<br />
<code>https://cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=[payload]</code><br />
<br />
'''Disable Logging:''' You can now disable debug logging</div>AlexGutierrezhttps://engineeringportal.nielsen.com//w/index.php?title=DCR_Video_%26_Static_Cloud_API&diff=3444DCR Video & Static Cloud API2019-02-15T21:06:32Z<p>AlexGutierrez: </p>
<hr />
<div>{{Breadcrumb|}} {{Breadcrumb|Digital}} {{Breadcrumb|DCR & DTVR}} {{CurrentBreadcrumb}}<br />
[[Category:Digital]]<br />
<br />
This guide shows you how to integrate the Nielsen Cloud API to enable Digital Content Ratings (DCR) measurement on your over-the-top (OTT) Apps.<br />
*For Roku Apps, please see [[DCR Video & Static Roku Cloud API]]<br />
*For Mobile Apps, please see [[DCR Video & Static Mobile Cloud API]]<br />
<br />
==Prerequisites==<br />
To get started, you will need a Nielsen App ID. The App ID is a unique ID assigned to your app. This will be provided to you upon starting the integration.<br />
<br />
<syntaxhighlight lang="javascript">XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX</syntaxhighlight><br />
<br />
==Integration==<br />
We will cover the steps for constructing the Cloud API Calls.<br />
<br />
===Request Overview===<br />
<br />
====URL Structure====<br />
<br />
The Cloud API Calls are HTTP GET Requests with the URL structure:<br />
<br />
<syntaxhighlight lang="javascript">[endpoint]/[appid]/[sessionID]/a?b=[payload]</syntaxhighlight><br />
<br />
The URL includes the following components:<br />
<br />
*<code>[endpoint]</code>: location of data collection environment<br />
*<code>[appid]</code>: provided App ID<br />
*<code>[sessionID]</code>: unique value for each user session<br />
*<code>[payload]</code>: metadata and events<br />
<br />
====Endpoint====<br />
<br />
There are endpoints for testing and production:<br />
<br />
*Testing: <code>https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/</code><br />
*Production: <code>https://cloudapi.imrworldwide.com/nmapi/v2/</code><br />
<br />
During testing, all calls should be pointed to the testing endpoint. We will review the update to the production endpoint during the Go Live section of this guide.<br />
<br />
====URL Example====<br />
As you move through the integration steps, you can reference the below URL structure with the expanded payload:<br />
<br />
<syntaxhighlight lang="javascript"><br />
https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=<br />
{<br />
"devInfo": [deviceInfo],<br />
"metadata": {<br />
"static": [static_metadata],<br />
"content": [content_metadata],<br />
"ad": [ad metadata]<br />
},<br />
"event": [event],<br />
"position": [playhead_position],<br />
"type": [asset type],<br />
"utc": [Unix time in ms]<br />
}<br />
</syntaxhighlight><br />
<br />
===Create Session ID===<br />
A unique Session ID must be created upon app launch and provided in the URL. This will allow measurement to occur for the entire duration that a user is within the app.<br />
<br />
A Session ID needs to be completely unique so it is recommended to use a version 4 UUID or another method of your choosing to guarantee there are no repeats.<br />
<br />
Upon exiting the app, the session will need to be terminated using the delete event. Sessions will automatically expire after 30 minutes of cloud inactivity.<br />
<br />
===Define URL Structure===<br />
Define the URL structure using your provided <code>[appid]</code> and a unique <code>[sessionID]</code>.<br />
<br />
<syntaxhighlight lang="javascript">https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=[payload]</syntaxhighlight><br />
<br />
===Configure Payload===<br />
<br />
All Cloud API requests must contain the following payload data:<br />
<br />
*''devInfo'': device and app info<br />
*''metadata'': asset metadata<br />
*''event metadata'': type of event<br />
<br />
The payload can be passed through key-values using the Nielsen reserved keys. The specific keys and descriptions are highlighted in the tables included in this section.<br />
<br />
'''Payload Example'''<br />
<br />
The example below should be referenced when following the steps for configuring the request payload.<br />
<br />
<syntaxhighlight lang="javascript"><br />
// 3.1 Configure Payload: devInfo <br />
payload = {<br />
"devInfo": {<br />
"devId": "AD-ID", <br />
"apn": "AppName",<br />
"apv": "1.0",<br />
"uoo": "false"<br />
},<br />
<br />
// 3.2 Configure Payload: metadata<br />
"metadata": {<br />
"static": {}, // object for measuring static content<br />
"content": { // object for measuring video content<br />
"type": "content", // "content" for video<br />
"assetid": "VIDEO-ID123", // unique ID for video<br />
"isfullepisode": "y", // full episode flag<br />
"program": "Program Name", // program name<br />
"title": "Episode Title S3 - EP1", // episode name<br />
"length": "1800", // content duration in seconds<br />
"segB": "Custom Segment B", // custom segment<br />
"segC": "Custom Segment C", // custom segment<br />
"crossId1": "Standard Episode ID", // episode ID<br />
"crossId2": "Content Originator ID", // content orginator (required for distributors)<br />
"airdate": "20161013 20:00:00", // airdate<br />
"adloadtype": "2" //ad load flag<br />
"hasAds": "1", // content contains ads = 1 / no ads = 0<br />
"progen": "CV" // program genre abbreviation<br />
},<br />
"ad": {<br />
"type": "preroll", // type of ad<br />
"assetid": "AD-ID123" // unique ID for ad<br />
}<br />
},<br />
<br />
// 3.3 Configure Payload: events<br />
"event": "playhead", //event name<br />
"position": "300", // position in seconds<br />
"type": "content", //"content" or "ad"<br />
"utc": "1456448742000" //unix timestamp in milliseconds <br />
}<br />
</syntaxhighlight><br />
<br />
=====Configure Payload: devInfo=====<br />
An object <code>"devInfo"</code> will need to be created to capture App and Device information.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| devId || unique ID to identify user (e.g. Advertising ID, Roku Device ID) || custom || Yes<br />
|-<br />
| apn || app name || custom || Yes<br />
|-<br />
| apv || app build version || custom || Yes<br />
|-<br />
| uoo || device opt-out status || <code>"true"</code> or <code>"false"</code> || Yes<br />
|-<br />
|}<br />
<br />
'''Example devInfo Object'''<br />
<syntaxhighlight lang="javascript"><br />
// create devInfo object<br />
"devInfo": {<br />
"devId": "AD-ID",<br />
"apn": "AppName",<br />
"apv": "1.0",<br />
"uoo": "false"<br />
},<br />
</syntaxhighlight><br />
<br />
==== 3.2 Configure Payload: metadata ====<br />
Asset metadata can be passed through <code>"metadata"</code>. There are two asset types: <code>"content"</code> for video and <code>"ad"</code> for ads. The metadata received for each asset is used for classification and reporting.<br />
<br />
You will need to set up <code>"metadata"</code> objects for <code>"content"</code> and <code>"ad"</code> with the required Nielsen keys as shown in the sample code below.<br />
<br />
===== Content Metadata =====<br />
Content metadata should remain constant throughout the entirety of an episode/clip including when ads play.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| type || type of asset || <code>"content"</code> || Yes<br />
|-<br />
| assetid || unique ID assigned to asset || custom || Yes<br />
|-<br />
| program ||name of program (25 character limit) || custom || Yes<br />
|-<br />
| title ||name of program (25 character limit) || custom || Yes<br />
|-<br />
| length || length of content in seconds || <code>seconds</code> (0 for live stream) || Yes<br />
|-<br />
| segB || custom segment B || custom || <br />
|-<br />
| segC || custom segment C || custom || <br />
|-<br />
| airdate || the airdate in the linear TV || YYYYMMDD HH24:MI:SS || Yes<br />
|-<br />
| isfullepisode || full episode flag || <code>"y"</code>- full episode, <code>"n"</code>- non full episode || Yes<br />
|-<br />
| crossId1 || standard episode ID || custom || Yes<br />
|-<br />
| crossId2 || content originator (only required for distributors) || Nielsen ||<br />
|-<br />
| adloadtype || type of ad load:<br />
<code>"1"</code> Linear – matches TV ad load<br />
<br />
<code>"2"</code> Dynamic – Dynamic Ad Insertion (DAI)<br />
|| <code>"2"</code> - DCR measures content with dynamic ads || Yes<br />
|-<br />
| hasAds || ads indicator<br />
<code>"1"</code>: ads included<br />
<br />
<code>"0"</code>: ads not included<br />
|| <code>"1"</code> or <code>"0"</code> || Yes<br />
|-<br />
| progen || Genre (required only for non-TV originated content). See [[DCR OTT Genre List]] for accepted values || <code>"CV"</code> for Comedy Variety || Required for non-TV originated content<br />
|}<br />
<br />
<br />
'''Example Content Object'''<br />
<syntaxhighlight lang='json'>// create content object<br />
"content": {<br />
"type": "content",<br />
"assetid": "VIDEO-ID123",<br />
"isfullepisode": "y",<br />
"program": "Program Name",<br />
"title": "Episode Title S3 - EP1",<br />
"length": "1800",<br />
"segB": "Custom Segment B",<br />
"segC": "Custom Segment C",<br />
"crossId1": "Standard Episode ID",<br />
"crossId2": "Content Originator ID",<br />
"airdate": "20161013 20:00:00",<br />
"adloadtype": "2",<br />
"hasAds": "1", <br />
"progen": "CV"<br />
}</syntaxhighlight><br />
<br />
===== Ad Metadata =====<br />
The ad metadata should be passed for each individual ad.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| type || type of ad || <code>"preroll"</code>, <code>"midroll"</code>, or <code>"postroll"</code> || Yes<br />
|-<br />
| assetid || unique ID assigned to ad || custom || Yes<br />
|}<br />
<br />
===== Example Ad Object =====<br />
<syntaxhighlight lang="javascript"><br />
// create ad object<br />
"ad": {<br />
"type": "preroll",<br />
"assetid": "AD-ID123"<br />
}<br />
</syntaxhighlight><br />
<br />
=== Configure Payload: Events ===<br />
<br />
The last part of the payload is for enabling events so content is measured correctly when viewed. The events and required parameters are included below.<br />
<br />
==== Event Types ====<br />
<br />
The available events are:<br />
{| class="wikitable"<br />
|-<br />
! Event !! Description<br />
|-<br />
| <code>"playhead"</code> || Playhead position in seconds. Must be passed as a whole number every 10 seconds. The final playhead position should also be sent before an asset has changed to properly capture full duration. Playhead is used to handle pause and scrubbing. When content is paused, stop passing playhead position.<br />
|-<br />
| <code>"complete"</code> || Complete event must be sent when the content has completed full playback or if a user has initiated a Stop. Before calling the complete event, a final playhead update with the final position is required to be sent to receive full duration credit. Complete does not need to be called if a user transitions videos (e.g. channel change or selecting a new VOD asset) before the video completes.<br />
|-<br />
| <code>"delete"</code> || The delete event should be sent when the application is exited and the viewing session is completed. After 30 minutes of inactivity, the session will expire automatically. All creditable duration will be summarized for all asset types when delete occurs (content and ads). Note that the delete event may not be feasible on certain platforms that do not offer the necessary callbacks to trigger this event.<br />
|}<br />
<br />
===== Event Parameters =====<br />
<br />
The following parameters need to be passed when calling events:<br />
<br />
{| class="wikitable"<br />
|-<br />
! Parameter !! Description !! Value !! Required<br />
|-<br />
| <code>"event"</code> || event type || <code>"playhead"</code>, <code>"complete"</code>, or <code>"delete"</code> || Yes<br />
|-<br />
| <code>"position"</code> || playhead position in seconds or Unix time in seconds || <code>"300"</code> || Yes<br />
|-<br />
| <code>"type"</code> || asset type || <code>"content"</code>, <code>"ad"</code> || Yes<br />
|-<br />
| <code>"utc"</code> || Unix timestamp in milliseconds. Must be passed every 10 seconds. || <code>"1472760000000"</code> || Yes<br />
|}<br />
<br />
===== Example Event =====<br />
You can call events by passing values in the required parameters:<br />
<br />
<syntaxhighlight lang="javascript"><br />
"devInfo": [deviceInfo],<br />
"metadata": {<br />
"static": [static metadata],<br />
"content": [content metadata],<br />
"ad": [ad metadata]<br />
},<br />
// Event Parameters<br />
"event": [event], // event name<br />
"position": [playheadPosition], //position in seconds<br />
"type": [asset type], // values are "content" or "ad"<br />
"utc": "1472760000000" //unix timestamp in milliseconds<br />
}<br />
</syntaxhighlight><br />
<br />
'''Note:''' The full payload including "devInfo" and "metadata" must be populated in each event request.<br />
<br />
===== Sample Event Lifecycle =====<br />
The sample event lifecycle can be used as a reference for identifying the order for calling events and values to pass.<br />
<br />
<syntaxhighlight lang="javascript"><br />
// Start of Session: session ID created when App is opened<br />
<br />
// Preroll<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
// Content<br />
Content Playhead {"event": "playhead", "position": "0", "type": "content", "utc": "1472760000000"} <br />
<br />
// Midroll<br />
Midroll Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
// Content resumes at 15 minutes<br />
Content Playhead {"event": "playhead", "position": "900", "type": "content", "utc": "1472760000000"} <br />
<br />
// Content completes at 30 minutes<br />
Complete {"event": "complete", "position": "1800", "type": "content", "utc": "1472760000000"} <br />
<br />
// Postroll<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
//End of Session: The delete event should be called when the App is exited. The values for position and type not required to be passed.<br />
Delete { "event": "delete", "position": "", "type": "", "utc": "1472760000000"} <br />
</syntaxhighlight><br />
<br />
<br />
'''Sample Event Lifecycle - Detailed Storyline'''<br />
This detailed event sequence provides additional insight for the correct events to call when handling certain playback scenarios.<br />
<syntaxhighlight lang='javascript'>// SESSION STARTS<br />
// Start of Session: session ID created when App is opened<br />
<br />
// PREROLL<br />
// Preroll Start - Start each Ad with a position of "0", resetting to '0' for each Ad, and Ad break.<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
// Preroll Stop - End each Ad with the final position of the Ad.<br />
Ad Playhead {"event": "playhead", "position": "15", "type": "ad", "utc": "1472761500000"}<br />
<br />
// CONTENT <br />
// Content Start - Start new content streams with a position of "0" incrementing the position every 10 seconds.<br />
Content Playhead {"event": "playhead", "position": "0", "type": "content", "utc": "1472761500000"} <br />
<br />
// Content Stop Before Ad Break - Send a playhead update including the current content positon before an Ad break.<br />
Content Playhead {"event": "playhead", "position": "299", "type": "content", "utc": "1472787400000"}<br />
<br />
// MIDROLL<br />
// Midroll Start - Start each Ad with a position of "0", resetting to '0' for each Ad, and Ad break.<br />
Midroll Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472787500000"} <br />
<br />
// Midroll Stop - End each Ad with the final position of the Ad.<br />
Ad Playhead {"event": "playhead", "position": "60", "type": "ad", "utc": "1472793500000"}<br />
<br />
// CONTENT<br />
// Content resumes at 5 minutes - Send playhead update with the current resumed position, and begin incrimenting the positon every 10 seconds.<br />
Content Playhead {"event": "playhead", "position": "300", "type": "content", "utc": "1472799500000"} <br />
<br />
// Content completes at 10:12 - Make sure to send in the playhead event with the final content position before sending the complete event.<br />
Final Content Playhead {"event": "playhead", "position": "612", "type": "content", "utc": "1472830700000"} <br />
<br />
Complete {"event": "complete", "position": "612", "type": "content", "utc": "1472830800000"} <br />
<br />
// POSTROLL<br />
// Postroll Start - Start each Ad with a position of "0", resetting to '0' for each Ad, and Ad break.<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472830900000"} <br />
<br />
// Postroll Stop - End each Ad with the final position of the Ad.<br />
Ad Playhead {"event": "playhead", "position": "45", "type": "ad", "utc": "1472835300000"}<br />
<br />
// SESSION ENDS<br />
<br />
//End of Session: The delete event should be called when the App is exited. The values for position and type not required to be passed.<br />
Delete { "event": "delete", "position": "", "type": "", "utc": "1472835400000"} </syntaxhighlight><br />
<br />
<br />
=====Handling Playhead=====<br />
Calling <code>"playhead"</code> is critical for accurate duration crediting. You can reference the below guidance to determine the correct playhead position to pass depending on the playback scenario.<br />
<br />
'''Playhead: General'''<br />
* Playhead position must start at 0 for each new asset, and be passed at least every 10 seconds.<br />
* Final postion must be sent at the end of content or an ad<br />
<br />
'''Playhead: Ads'''<br />
* The final position must be sent when switching from content to ad, or ad to content.<br />
* Each ad playhead position should be 0 at ad start.<br />
* For Ad Pods, playhead must be called, and reset to 0 for each individual ad. <br />
* The last content position before an Ad should be sent before switching to Ads.<br />
* When content has resumed following an ad break, the playhead position update must continue where the previous content segment left off.<br />
<br />
'''Playhead: User Actions'''<br />
* Upon user scrubbing, the current position must be sent before a user scrubs, and the new position should be sent where the user lands, and begin sending in the 10 second updates thereafter.<br />
* On pause, send the current position and then discontinue sending playhead event updates.<br />
* If a user exits a stream early, the last current position must be sent in a playhead update to receive accurate duration.<br />
<br />
===== Interruption Scenarios =====<br />
<br />
As part of configuring events, you will need to handle all possible interruption scenarios such as:<br />
<br />
*Wi-Fi OFF / ON<br />
*App going Background / Foreground (Video players only, not for Audio players)<br />
*App Crash or Exit<br />
<br />
When playback is interrupted, the app needs to send delete immediately.<br />
<br />
Once playback resumes, a new session will need to be created with a unique session ID. All of the required metadata and events will need to be sent.<br />
<br />
'''Note:''' The session will automatically timeout after 30 minutes of inactivity.<br />
<br />
=== Example Request ===<br />
<br />
Now that we walked through the Cloud API integration steps, your requests should have the following components: Session ID, App ID, and Payload. You can reference the example below when your reviewing your integration.<br />
<br />
<syntaxhighlight lang="javascript"><br />
// 1. Create Session ID<br />
sessionID = dfc7dc6a-66a7-4705-9fba-adaaf7e3d5e0 // Example sessionID created using a UUID Generator<br />
<br />
// 2. Define URL Structure with App ID and Session ID<br />
sessionURL = https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=<br />
<br />
// 3. Configure Payload<br />
// 3.1 Configure Payload: devInfo <br />
payload = {<br />
"devInfo": {<br />
"devId": "AD-ID", <br />
"apn": "AppName",<br />
"apv": "1.0",<br />
"uoo": "false"<br />
},<br />
<br />
// 3.2 Configure Payload: metadata<br />
"metadata": {<br />
"static": {}, // object for measuring static content<br />
"content": { // object for measuring video content<br />
"type": "content", // "content" for video<br />
"assetid": "VIDEO-ID123", // unique ID for video<br />
"isfullepisode": "y", // full episode flag<br />
"program": "Program Name", // program name<br />
"title": "Episode Title S3 - EP1", // episode name<br />
"length": "1800", // content duration in seconds<br />
"segB": "Custom Segment B", // custom segment<br />
"segC": "Custom Segment C", // custom segment<br />
"crossId1": "Standard Episode ID", // episode ID<br />
"crossId2": "Content Originator ID", // content orginator (required for distributors)<br />
"airdate": "20161013 20:00:00", // airdate<br />
"adloadtype": "2", // ad load flag<br />
"hasAds": "1", // content contains ads = 1 / no ads = 0<br />
"progen": "CV" // program genre abbreviation<br />
},<br />
"ad": {<br />
"type": "preroll", // type of ad<br />
"assetid": "AD-ID123" // unique ID for ad<br />
}<br />
},<br />
<br />
// 3.3 Configure Payload: events<br />
"event": "playhead", //event name<br />
"position": "300", // position in seconds<br />
"type": "content", //"content" or "ad"<br />
"utc": "1456448742000" //unix timestamp in milliseconds <br />
}<br />
<br />
// Append payload to URL<br />
var image = new Image()<br />
image.onerror = function() {<br />
// wait and send again<br />
}<br />
(new Image).src = sessionURL+encodeURI(JSON.stringify(payload));<br />
</syntaxhighlight><br />
<br />
==== Enable Debug Logging ====<br />
<br />
Now that you have set up the Cloud API requests, you can enable debug logging to validate your integration. Enabling debug logging is required for Nielsen certification.<br />
<br />
==== GET Request ====<br />
<br />
Display GET Request to console using a name to identify each event (e.g. playhead).<br />
<br />
<syntaxhighlight lang="javascript"><br />
console.log("Event", image); <br />
</syntaxhighlight><br />
<br />
==== Payload ====<br />
<br />
Output payload to identify required metadata and events.<br />
<br />
<syntaxhighlight lang="javascript"><br />
console.log("Event Payload", payload); <br />
</syntaxhighlight><br />
<br />
==== HTTP Response Code ====<br />
<br />
Confirm request was completed by viewing HTTP response code.<br />
<br />
<syntaxhighlight lang="javascript"><br />
code = msg.GetResponseCode();<br />
console.log("Response Code", code); <br />
</syntaxhighlight><br />
<br />
You can reference the HTTP Response Code table when reviewing your requests:<br />
<br />
{| class="wikitable"<br />
|-<br />
! Status Code !! Status Text !! Description<br />
|-<br />
| <code>200</code> || OK || request received<br />
|-<br />
| <code>403</code> || Forbidden || invalid App ID<br />
|-<br />
| <code>404</code> || Not Found || JSON issue<br />
|}<br />
<br />
== Opt-Out ==<br />
Your app must provide a means for the user to Opt-Out, or Opt-In to Nielsen Measurement. This requirement can be fulfilled by checking the device OS for the user's setting of "Limit Ad Tracking" or similar option. If the device offers "Limit Ad Tracking" settings, you should set <code>uoo=true</code> or <code>uoo=false</code> depending on the user's privacy setting. Also, ensure that the <code>devId</code> is set to a blank value if the user elects to opt-out.<br />
<br />
If the device does not have OS-level "Do Not Track" settings, you can implement opt-out by creating an Opt-Out/Opt-In button, toggle switch, or slider within the app "Settings", or "About" section to allow the user selection.<br />
<br />
[[File:Nielsen Opt-Out.png|link=]]<br />
<br />
You will need to store the User Opt-Out (uoo) status, so that it can be retrieved and populated in the <code>"devInfo"</code> metadata which will be sent in every Cloud API event (playhead, complete, & delete). <br />
<br />
{| class="wikitable"<br />
|-<br />
! uoo Key !! Description !! Values<br />
|-<br />
| uoo || Device is Opted-In to Nielsen Measurement (Recommended Default Setting) || <code>"false"</code><br />
|-<br />
| uoo || Device is Opted-Out of Nielsen Measurement || <code>"true"</code><br />
|}<br />
<br />
===== devInfo Opt-In JSON Payload Example =====<br />
<br />
<syntaxhighlight lang="javascript"><br />
"devInfo": {<br />
"apn": "Cloud API Sample App",<br />
"apv": "1",<br />
"devId": "7be25cf9-8c40-5cc2-871e-19bf41940288",<br />
"uoo": "false"<br />
},<br />
</syntaxhighlight><br />
<br />
===== devInfo Opt-Out JSON Payload Example =====<br />
<br />
<syntaxhighlight lang="javascript"><br />
"devInfo": {<br />
"apn": "Cloud API Sample App",<br />
"apv": "1",<br />
"devId": "", //devId must be blank when a user elects to Opt-Out.<br />
"uoo": "true"<br />
},<br />
</syntaxhighlight><br />
<br />
===== Privacy Information Template To Include In Opt-Out Screen =====<br />
<br />
<br />
Additionally, all applications must display the Nielsen privacy policy within their application, typically in the Settings/About screen of your application. The Nielsen privacy policy text is listed below.<br />
<br />
<blockquote><br />
'''''ABOUT NIELSEN MEASUREMENT'''''<br />
<br />
Television and the way we watch it have come a long way since Nielsen began measuring TV audiences in 1950. Today, the ability to watch our favorite shows at any time and on multiple devices amplifies the need for exceptionally adept and flexible audience measurement capabilities.<br />
<br />
Consumers are changing with the times, and the same goes for us. As technology continues to evolve and media companies try new ways to attract viewers, understanding what consumers are watching — and what they're watching on — is more important than ever. Today, viewing video is a personal and mobile experience — anytime and anywhere. Our capabilities provide relevant metrics that are necessary to inform successful marketing and programming and drive continued growth. As a global information and measurement leader, we are committed to protecting the privacy and security of the data we collect, process and use. While our digital measurement products are not used to identify you in any way, they help us and our clients measure and analyze how consumers engage with media across online, mobile and emerging technologies, and offer insights into consumer behavior.<br />
<br />
'''''YOUR CHOICES'''''<br />
<br />
Nielsen believes that you should have a choice about whether to contribute to our research and insights. To opt out or opt in to Nielsen measurement, please toggle your "Limit Ad Tracking" (or similar setting) on your device, or make the appropriate selection within the application's settings. If you have this app on more than one device, you will need to opt out of this app on each device. To learn more about our digital measurement products and your choices in regard to them, please visit http://www.nielsen.com/digitalprivacy.<br />
<br />
'''''Disclosure Template'''''<br />
<br />
Additionally, you must update the App description information from the App Distribution Store with the Nielsen Measurement disclosure below:<br />
<br />
This app features Nielsen's proprietary measurement software which will allow you to contribute to market research, like Nielsen's TV Ratings. Please see http://www.nielsen.com/digitalprivacy for more information.<br />
</blockquote><br />
<br />
== Testing ==<br />
Before providing an app build to Nielsen for testing, it is important to run validation checks once you have enabled debug logging.<br />
<br />
=== Payload Validation ===<br />
<br />
Ensure that all of the required payload data is populating while testing several videos. The following areas are critical to measurement:<br />
*devInfo<br />
*Asset metadata for both content, and ads<br />
*Events<br />
*Opt-Out status<br />
<br />
=== Player Events ===<br />
Review event calls:<br />
<br />
==== playhead ====<br />
*Playhead position updates every 10 seconds starting at position '0' for each new asset.<br />
*Final playhead position is sent on content, or ad before switching between assets.<br />
*Content metadata remains constant throughout an episode, or clip play.<br />
*Ad metadata is populated appropriately for each individual ad.<br />
*Playhead position update resumes for content after an ad break, and resets to 0 for each individual ad.<br />
*For scrubbing, last current position should be sent while scrubbing occurs, and the new position should also be sent where the user scrubs to.<br />
*Exiting a stream early should execute the last current position in a playhead update to receive accurate duration.<br />
*Upon pause, the current position should be sent, and playhead updates should stop incrementing until resume play occurs.<br />
<br />
==== complete ====<br />
*Check that the complete event executes upon content complete after the final playhead update is sent<br />
*Do not execute the complete event for ads<br />
<br />
==== delete ====<br />
*Check to see that the delete event occurs upon app exit, if the platform has the necessary exit callback events.<br />
<br />
==== GET Request Format ====<br />
*Ensure that the event payloads are formatted in JSON<br />
*Check to see that each of the Cloud API GET requests are properly encoded<br />
<br />
==== HTTP Response ====<br />
*Make sure that each of the Cloud API Get requests are received by the Nielsen Cloud API properly through use of the HTTP Response Code outputs enabled in console.<br />
<br />
==== Opt-Out ====<br />
*Test the "uoo" key gets populated accurately for both Opt-In and Opt-Out selections by validating the Cloud API events called after the user Opt-Out/Opt-In selection.<br />
*Test that the devId field is populated with a blank value if a user has elected to Opt-Out. For example: "devId": "",<br />
*If the device supports "Limit Ad Tracking" or has device "Opt-Out" settings, test that uoo=true, and that devId is set to a blank value if enabled in the device settings.<br />
<br />
== Go Live ==<br />
After your integration has been certified, you will need to: Change Endpoint and Disable Logging.<br />
<br />
'''Change Endpoint:''' You will need to update to the production endpoint:<br />
<br />
*Testing: <code>https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/</code><br />
*Production: <code>https://cloudapi.imrworldwide.com/nmapi/v2/</code><br />
<br />
Your production URL structure should now be:<br />
<code>https://cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=[payload]</code><br />
<br />
'''Disable Logging:''' You can now disable debug logging</div>AlexGutierrezhttps://engineeringportal.nielsen.com//w/index.php?title=DCR_Video_iOS_SDK&diff=3443DCR Video iOS SDK2019-02-15T19:59:56Z<p>AlexGutierrez: Removed extra character from playhead description</p>
<hr />
<div>{{Breadcrumb|}} {{Breadcrumb|Digital}} {{Breadcrumb|DCR & DTVR}} {{CurrentBreadcrumb}}<br />
[[Category:Digital]]<br />
<br />
== Overview ==<br />
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.<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), and [[Digital Audio]]. Nielsen SDKs are also equipped to measure static content and can track key life cycle events of an application like:<br />
*Application launch events and how long app was running<br />
*Time of viewing a sub section / page in the application.<br />
<br />
== Prerequisites ==<br />
To start using the App SDK, the following items are required:<br />
{| class="wikitable"<br />
|-<br />
! style="width: 30px;" |<br />
! style="width: 15%;" | Item<br />
! Description<br />
! Source<br />
|-<br />
|| ☑ || '''App ID (appid)''' || Unique ID assigned to the player/site and configured by product. || Contact Nielsen<br />
|-<br />
|| ☑ || '''sfcode''' || Environment that the SDK must point to || Contact Nielsen<br />
|-<br />
|| ☑ || '''Nielsen SDK''' || Includes SDK libraries and '''sample implementation'''; ''See [[iOS SDK Release Notes]]'' || [[Special:Downloads|Download]]<br />
|}<br />
<br />
If you do not have any of these pre-requisites or if you have any questions, please contact our SDK sales support team.<br />
Refer to [[Digital Measurement Onboarding]] guide for information on how to get a Nielsen App SDK and appid.<br />
___TOC___<br />
== Implementation ==<br />
This guide covers implementation steps for iOS using Xcode utilizing the Standard Nielsen SDK for DCR.<br />
<br />
== Setting up your Development Environment ==<br />
Prior to SDK Version 6.2.0.0 the IOS framework has been distributed as a static library packaged into framework bundle format. Apple recommends to use dynamic framework, it has some benefits over static libraries like less executable file size of an app, faster startup time and native support in xCode IDE. Nielsen AppSDK has been transformed into dynamic framework in this release ([[iOS_Static_Framework_Setup|static framework]] is still available).<br />
<br />
If migrating from the static library to this new dynamic framework, once implemented, unless your specific application requires, you can remove the following Frameworks that were once required:<code> [AdSupport, JavascriptCore, SystemConfiguration, Security, AVFoundation, libc++] </code><br />
<br><br />
<br />
The Dynamic framework is created as a fat framework. It means that it contains slices required for devices (armv7, arm64) as well as slices required for simulators (i386, x86_64). Simulator slices are needed to let clients build and debug their app on the simulators, but they should be removed before sending the app to the AppStore. The example of the shell script that should be added as a Run Script phase in the application can be [[DCR_Video_iOS_SDK#Removing_Simulators|found below]].<br />
<br />
=== How to obtain the NielsenAppApi.Framework ===<br />
The Nielsen AppSDK can either be downloaded directly or can be integrated directly within an application through the use of a CocoaPod or Gradle.<br />
* [[Special:Downloads|Select to Download Directly]]<br />
* [[Digital_Measurement_iOS_Artifactory_Guide|Select to obtain Cocoapod implementation guide]]<br />
<br />
=== Configuring Xcode Development Environment ===<br />
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<br />
<br />
<blockquote>'''Note''': All communications between the SDK and the Census (Collection Facility) use HTTPS.</blockquote><br />
<br />
=== Download Framework ===<br />
The first step is to download and copy the [[Special:Downloads|NielsenAppApi.framework]] bundle to the app project directory.<br />
=== Add Framework ===<br />
In the General tab for app configuration add NielsenAppApi.framework in the list of Embedded Binaries.<br />
=== Add Path ===<br />
Add path to the NielsenAppApi.framework in the Framework Search Paths build setting.<br />
=== Import Framework ===<br />
Add NielsenAppApi.framework module in the source file of your app:<br />
<br />
==== Using Swift ====<br />
To import a set of Objective-C files in the same app target as your Swift code, you rely on an Objective-C bridging header to expose those files to Swift. Xcode offers to create this header file when you add a Swift file to an existing Objective-C app, or an Objective-C file to an existing Swift app.<br />
*Select File/New File/Objective-C File<br />
*Xcode will prompt you to create a bridging header.<br />
[[File:bridgingheader 2x.png|600px|center|link=]]<br />
Once this file has been created, you need to add the following:<br />
<syntaxhighlight lang="swift"><br />
#import <NielsenAppApi/NielsenAppApi.h><br />
</syntaxhighlight><br />
<br />
==== Using Objective-C ====<br />
Add the code to the View Controller’s header file.<br />
<syntaxhighlight lang ="objective-c"><br />
#import <NielsenAppApi/NielsenAppApi.h><br />
</syntaxhighlight><br />
<br />
== SDK Initialization ==<br />
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)<br />
<br />
* A maximum of four SDK instances per appid are supported. [[Dual_Instances_of_SDK|(Click here for an example of multiple instances)]]<br />
* When four SDK instances exist, you must destroy an old instance before creating a new one.<br />
<br />
The following table contains the list of arguments that can be passed via the AppInfo JSON schema.<br />
<br />
* The appid is provided by the Nielsen Technical Account Manager (TAM). The appid is a GUID data type and is specific to the application.<br />
{| class="wikitable"<br />
|-<br />
! Parameter / Argument !! Description !! Source !! Required? !! Example<br />
|-<br />
| appid || Unique id for the application assigned by Nielsen. It is GUID data type.|| Nielsen-specified || Yes || PXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX<br />
|-<br />
| appname || Name of the application || Client-defined || Optional; automatically detected in SDK 6.0.0.4 and above || Nielsen Sample App<br />
|-<br />
| sfcode || Nielsen collection facility to which the SDK should connect.<br />
'''DTVR'''<br />
* "us"<br />
'''Digital Audio'''<br />
* "drm"<br />
'''DCR'''<br />
* "dcr" <br />
|| Nielsen-specified || Yes || dcr<br />
|-<br />
|containerID || View ID of the UI element used as player view in application for Viewability ||Client-defined||Optional||"1234567"<br />
|-<br />
| nol_devDebug || Enables Nielsen console logging. Only required for testing<br />
|| Nielsen-specified || Optional || "DEBUG"<br />
|}<br />
<br />
== Debug flag for development environment ==<br />
Player application developers / integrators can use Debug flag to check whether an App SDK API call made is successful. To activate the Debug flag,<br />
Pass the argument <code>@"nol_devDebug":@"INFO"</code>, in the JSON string . The permitted values are:<br />
<br />
* '''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.<br />
* '''WARN''': Indicates potential integration / configuration errors or SDK issues.<br />
* '''ERROR''': Indicates important integration errors or non-recoverable SDK issues.<br />
* '''DEBUG''': Debug logs, used by the developers to debug more complex issues.<br />
<br />
Once the flag is active, it logs each API call made and the data passed. The log created by this flag is minimal.<br />
<blockquote>'''Note''': DO NOT activate the Debug flag in a production environment.</blockquote><br />
<br />
=== Sample SDK Initialization Code ===<br />
{{ExampleCode|<br />
|Swift = <br />
Swift 4.0 Example:<br />
<code>NielsenInit.swift</code><br />
<syntaxhighlight lang="swift"><br />
import Foundation<br />
import NielsenAppApi<br />
<br />
class NielsenInit : NSObject {<br />
class func createNielsenApi(delegate: NielsenAppApiDelegate) -> NielsenAppApi?{<br />
<br />
let appInformation:[String: String] = [<br />
<br />
"appid": "PDA7D5EE6-B1B8-4123-9277-2A788XXXXXXX",<br />
"sfcode": "dcr",<br />
"nol_devDebug": "DEBUG"<br />
"containerId": String(containerId) //Keep container id unique constant, you can use tag property of player.<br />
]<br />
<br />
return NielsenAppApi(appInfo:appInformation, delegate:delegate)<br />
}<br />
}<br />
</syntaxhighlight><br />
<br />
Sample code using AVPlayer.<br />
<code>ViewController.swift</code><br />
<br />
<syntaxhighlight lang="swift"><br />
class ViewController: UIViewController, NielsenAppApiDelegate, AVPlayerViewControllerDelegate {<br />
<br />
// your code// <br />
<br />
override func viewDidLoad() {<br />
super.viewDidLoad()<br />
<br />
//Getting the instance of NielsenApi<br />
self.nielsenApi = NielsenInit.createNielsenApi(delegate: self)<br />
<br />
}<br />
}<br />
</syntaxhighlight><br />
|Objective C = <br />
Initialize the Nielsen App object within the viewDidLoad view controller delegate method using initWithAppInfo:delegate:<br />
<blockquote>If App SDK is initialized using init or new methods, it will ignore the API calls resulting in no measurement. The SDK will not return any errors.</blockquote><br />
<syntaxhighlight lang="objective-c"> <br />
#import "NielsenInit.h"<br />
#import <NielsenAppApi/NielsenEventTracker.h><br />
<br />
@implementation NielsenInit<br />
<br />
+ (NielsenEventTracker *)createNielsenEventTrackerWithDelegate:(id<NielsenEventTrackerDelegate>)delegate<br />
{<br />
//Initialising the NielsenEventTracker class by passing app information which returns the instance of NielsenEventTracker.<br />
<br />
NSDictionary *appInformation = @{ @"appid": @"PDA7D5EE6-B1B8-4123-9277-2A788XXXXXXX",<br />
@"appversion": @"1.0",<br />
@"sfcode": @"dcr",<br />
@"nol_devDebug": @"DEBUG",<br />
@"containerId": @"1" };<br />
<br />
return [[NielsenEventTracker alloc] initWithAppInfo:appInformation delegate:delegate];<br />
}<br />
<br />
@end<br />
</syntaxhighlight><br />
<br />
<br />
The following would be the <code>NielsenInit.h</code> file:<br />
<syntaxhighlight lang="objective-c"><br />
<br />
#import <Foundation/Foundation.h><br />
<br />
@class NielsenEventTracker;<br />
@protocol NielsenEventTrackerDelegate;<br />
<br />
@interface NielsenInit : NSObject<br />
<br />
+ (NielsenEventTracker *)createNielsenEventTrackerWithDelegate:(id<NielsenEventTrackerDelegate>)delegate;<br />
<br />
@end<br />
</syntaxhighlight><br />
<br />
}}<br />
<br />
<!--<br />
== Initializing the Nielsen AppSDK to measure the Viewability ==<br />
The integrator to support the viewability metrics in the application has to provide a tag value of the player view to let Nielsen AppSDK know that there is a player that needs to be tracked. It’s called the ‘containerId’ and it should be passed in application info dictionary as string while initializing the Nielsen AppSDK.<br />
<br />
=== Android ===<br />
{| class="wikitable"<br />
|-<br />
! # !! Parameter Name !! Description !! Supported Values !! Example<br />
|-<br />
| 1 || containerId ||View ID of the UI element used as player view in application. getId() method of View class can be used to get this value. ||A positive integer used to identify the view. || 2131558561<br />
|}<br />
<br />
=== iOS ===<br />
{| class="wikitable"<br />
|-<br />
! # !! Parameter Name !! Description !! Supported Values !! Example<br />
|-<br />
| 1 || containerId ||The tag of the UIView that represents the Player View ||The string value representing the NSInteger value with maximum value of NSIntegerMax that is related on 32- or 64-bit applications. || "100" <br> "2131558561"<br />
|}<br />
<br />
For iOS it is required to link additional frameworks that are needed for viewability engine:<br><br />
<code>JavaScriptCore.framework</code> <br><br />
<code>WebKit.framework</code><br />
<br />
The Nielsen AppSDK uses a tracking WebView (TWV) approach. For more information on Viewability, please refer to [https://engineeringportal.nielsen.com/docs/Implementing_Viewability_with_AppSDK Implementing Viewability with AppSDK.]<br />
--><br />
<br />
== APP SDK Error & Event Codes ==<br />
To view the Error and Event codes for iOS and Android, please review the [[APP SDK Event Codes|App SDK Event Code]] Reference page.<br />
<br />
== Configure Payload ==<br />
=== Handling JSON Metadata ===<br />
All the SDK methods handles only two types of objects: NSString, NSDictionary. The parameters passed must be either a JSON formatted string or a NSDictionary object. The JSON passed in the SDK must be well-formed.<br />
* NSDictionary object<br />
** If an object of unexpected type is passed to the method, the error message will be logged.<br />
** If string has invalid JSON format, the error message will be logged.<br />
* JSON value must be string value.<br />
** This includes boolean and numeric values. For example, a value of true should be represented with "true", number value 123 should be "123".<br />
** All the Variable Names like appid, appname, sfcode, dataSrc, title, type etc. are case-sensitive. Use the correct variable name as specified in the documentation.<br />
* JSON string can be prepared using either raw NSString or serialized NSDictionary.<br />
{{ExampleCode|<br />
|Swift = <br />
<syntaxhighlight lang="swift"><br />
<br />
let channelInfo = [<br />
"channelName": "My Channel Name 1",<br />
];<br />
<br />
let contentMetadata = [<br />
"type": "content",<br />
"assetid": "88675545",<br />
"title": "Program S3, EP1",<br />
"isfullepisode":"No",<br />
"program":"Program Name",<br />
"length":"3600",<br />
"airdate":"20171020 10:05:00"<br />
"segB":"CustomSegmentValueB", //optional<br />
"segC":"CustomSegmentValueC", //optional<br />
];<br />
</syntaxhighlight> <br />
|Objective C = <syntaxhighlight lang="objective-c"> <br />
NSDictionary *channelInfo = @<br />
{<br />
@"channelname":@"My Channel Name 1",<br />
}<br />
<br />
NSDictionary *contentMetadata = @<br />
{@"type": @"content",<br />
@"assetid":@"C77664",<br />
@"title":@"S2,E3",<br />
@"isfullepisode":@"y",<br />
@"program":@"MyProgram",<br />
@"length": @"3600",<br />
@"airdate":@"20180120 10:00:00",<br />
@"adloadtype":@"2",<br />
@"segB": @"CustomSegmentValueB", //optional<br />
@"segC": @"CustomSegmentValueC", //optional<br />
}<br />
</syntaxhighlight><br />
}}<br />
<br />
=== Configure metadata === <br />
channelName should remain constant throughout the completion of an episode or live stream.<br />
{| class="wikitable"<br />
|-<br />
! Key !! Description !! Values !! Required<br />
|-<br />
| channelName || ChannelInfo refers to the Channel name. This can be a free-form value<br />
value such as a friendly name for the content being played. the SDK<br/><br />
will pass the application name automatically.<br />
|| custom || No<br />
|-<br />
|}<br />
<br />
=== Content metadata ===<br />
Content metadata should remain constant throughout the entirety of an episode/clip including when ads play.<br />
{{DCR Content Metadata}}<br />
<br />
=== Ad Metadata ===<br />
The ad metadata (if applicable) should be passed for each individual ad, if ads are available during or before the stream begins.<br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| type || ad vs content || <code>'ad'</code>|| ✓<br />
|-<br />
| assetid || unique ID assigned to ad || custom || ✓<br />
|}<br />
<br />
=== Example Ad Object ===<br />
<syntaxhighlight lang="javascript"><br />
// create ad object<br />
"ad": {<br />
"type": "preroll",<br />
"assetid": "AD-ID123"<br />
}<br />
</syntaxhighlight><br />
<br />
== Configure API Calls ==<br />
[[File:appsdkTimeline-DCR.png|icon|link=]]<br />
=== Sample API Sequence ===<br />
A Sample API sequence could follow this flow:<br />
{| class="wikitable"<br />
|-<br />
! Type !! Sample code !! Description<br />
|-<br />
|On App Start||<code>[nielsenMeter loadMetadata: contentMetadata];</code> || // contentMetadata Object contains the JSON metadata for the impression<br />
|-<br />
| rowspan="2" | Start of stream || <code>[nielsenMeter play: channelName];</code> || // channelName now automatically generated by Nielsen SDK<br />
|-<br />
| <code>[nielsenMeter loadMetadata: contentMetadataObject];</code> || // contentMetadataObject contains the JSON metadata for the content being played<br />
|-<br />
| Content || <code>[nielsenMeter playheadPosition: position];</code> || // playheadPosition is position of the playhead while the content is being played<br />
|-<br />
| End of Stream || <code>[nielsenMeter end];</code> || // Content playback is completed.<br />
|}<br />
<br />
=== SDK Events ===<br />
{| class="wikitable"<br />
|-<br />
! Event !! Parameter !! Description<br />
|-<br />
| 'loadMetadata' || content/ad metadata object || Needs to be called at the beginning of each asset<br />
|-<br />
| 'playheadPosition' || playhead position as integer<br/><br />
VOD: current position in seconds <br/><br />
Live: current UNIX timestamp (seconds since Jan-1-1970 UTC) <br/><br />
Note: 'PlayheadPosition' has to be called every second<br />
||<br />
Pass playhead position every second during playback<br />
|-<br />
| 'stop' || playhead position || Call when content or ads complete playing and pass playhead position<br />
|-<br />
| 'end' || playhead position in seconds || Call when the current video asset completes playback and pass the playhead position. <br/><br />
Example: At the end of the content stream, if the user switches to another piece of content, when the browser is refreshed or closed.<br />
|}<br />
<blockquote>Note: For livestream, send the UNIX timestamp, for VOD send the time in seconds as integer. The final playhead position must be sent for the current asset being played before calling <code>'''stop'''</code>, <code>'''end'''</code> or<code> '''loadmetadata'''</code>,.</blockquote><br />
<br />
=== Life cycle of SDK instance ===<br />
Life cycle of SDK instance includes four general states:<br />
# '''Initial state''' – The SDK is not initialized and hence, not ready to process playing information. Once the SDK is moved out of this state, it needs instantiation of the new SDK instance in order to get the instance in the '''Initial state'''.<br />
# '''Idle state''' – The SDK is initialized and is ready to process playing information. Once Initialized, the SDK instance is not processing any data, but is listening for the play event to occur.<br />
# '''Processing state''' – The SDK instance is processing playing information. The <code>'''play'''</code> and <code>'''loadMetadata''' </code> calls move the SDK instance into this state. In this state, the SDK instance will be able to process the following calls.<br />
## <code>'''playheadPosition'''</code> – Call this API every one second when playhead position timer is fired. If a LIVE event, use the current UNIX timestamp (seconds since Jan-1-1970 UTC).<br />
## <code>'''stop'''</code> – Call this API when the playback is paused, switches between content and ad (within the same content playback) or encounters interruptions.<br />
## <code>'''end'''</code> – SDK instance exits from Processing state when this API is called.<br />
# '''Disabled state''' – The SDK instance is disabled and is not processing playing information. SDK instance moves into this state in one of the following scenarios.<br />
## Initialization fails<br />
## <code>'''appDisableApi'''</code> is set to <code>true</code><br />
<br />
<blockquote>'''Note:''' For API Version 5.1 and above, App SDK will fire data pings and continue measurement even after the user has opted out from Nielsen measurement on a device. The data ping will be marked as opted-out ping.<br />
<br />
'''Note''': In case of any interruptions during playback due to alarm, calendar, call, flight mode, Wi-Fi toggle, channel change, etc., call [[stop]] to stop the measurement.<br />
* As soon as the playback resumes, call <code>'''play'''</code>, <code>'''loadMetadata''' </code> and <code>'''playheadPosition'''</code> </blockquote><br />
<br />
=== API Call Sequence ===<br />
==== Use Case 1: Content has no Advertisements ====<br />
Call [[play()]] with channelName JSON as below.<br />
<syntaxhighlight lang="json">{<br />
"channelName": "TheMovieTitle"<br />
}</syntaxhighlight><br />
Call [[loadMetadata()]] with JSON metadata for content as below.<br />
<syntaxhighlight lang="json">{<br />
"type": "content",<br />
"assetid": "vid345-67483",<br />
"program": "ProgramName",<br />
"title": "Program S3, EP1",<br />
"length": "3600",<br />
...<br />
}</syntaxhighlight><br />
Call [[setPlayheadPosition()]] every one second until a pause / stop.<br />
Use the sample API sequence below as a reference to identify the specific events that need to be called during content playback without ads.<br />
{| class="wikitable"<br />
|-<br />
! Type !! Sample code !! Description<br />
|-<br />
| rowspan="2" | Start of stream || <code>mAppSdk.play(channelName); </code> || // channelName contains JSON metadata of channel/video name being played<br />
|-<br />
| <code>mAppSdk.loadMetadata(contentMetaDataObject);</code> || // contentMetadataObject contains the JSON metadata for the content being played<br />
|-<br />
| Content || <code>mAppSdk.setPlayheadPosition(playheadPosition);</code> || // position is position of the playhead while the content is being played<br />
|-<br />
| End of Stream || <code>mAppSdk.end();</code> || // Content playback is completed.<br />
|}<br />
<br />
==== Use Case 2: Content has Advertisements ====<br />
Call [[play()]] with channelName JSON as below.<br />
<syntaxhighlight lang="json">{<br />
"channelName": "TheMovieTitle"<br />
}</syntaxhighlight><br />
Call [[loadMetadata()]] with JSON metadata for ad as below.<br />
<syntaxhighlight lang="json">{<br />
"type": "preroll",<br />
"assetid": "ad=123"<br />
}</syntaxhighlight><br />
<blockquote>Note: In case the individual ad details are not available, send ad pod (presence) details through the [[loadMetadata]] and playhead position through [[playheadPosition]].</blockquote><br />
<br />
Call [[setPlayheadPosition()]] every one second until a pause / stop / another [[loadMetadata()]] is called. Playhead should be passed for the entire duration of ad pod, if the ad pod details are passed as part of [[loadMetadata()]].<br />
<br />
The sample API sequence can be used as a reference to identify the specific events that need to be called during content and ad playback.<br />
{| class="wikitable"<br />
|-<br />
! Type !! Sample code !! Description<br />
|-<br />
| rowspan="2" | Start of stream || <code>mAppSdk.play(channelName); </code> || // channelName contains JSON metadata of channel/video name being played<br />
|-<br />
| <code>mAppSdk.loadMetadata(contentMetaDataObject);</code> || // contentMetadataObject contains the JSON metadata for the content being played<br />
|-<br />
| rowspan="3" | Preroll || <code>mAppSdk.loadMetadata(prerollMetadataObject);</code> || // prerollMetadataObject contains the JSON metadata for the preroll ad<br />
|-<br />
| <code>mAppSdk.setPlayheadPosition(playheadPosition);</code> || // position is position of the playhead while the preroll ad is being played<br />
|-<br />
| <code>mAppSdk.stop();</code> || // Call stop after preroll occurs<br />
|-<br />
| rowspan="3" | Content || <code>mAppSdk.loadMetadata(contentMetaDataObject);</code> || // contentMetadataObject contains the JSON metadata for the content being played<br />
|-<br />
| <code>mAppSdk.setPlayheadPosition(playheadPosition);</code> || // position is position of the playhead while the content is being played<br />
|-<br />
| <code>mAppSdk.stop();</code> || // Call stop after the content is paused (ad starts)<br />
|-<br />
| rowspan="6" | Midroll || <code>mAppSdk.loadMetadata(midrollMetaDataObject);</code> || // midrollMetadataObject contains the JSON metadata for the midroll ad<br />
|-<br />
| <code>mAppSdk.setPlayheadPosition(playheadPosition);</code> || // position is position of the playhead while the midroll ad is being played<br />
|-<br />
| <code>mAppSdk.stop();</code> || // App moves to background(midroll pauses) <br />
|-<br />
| <code>mAppSdk.loadMetadata(midrollMetaDataObject);</code> || // App moves to foreground (midroll resumes) <br />
|-<br />
| <code>mAppSdk.setPlayheadPosition(playheadPosition);</code> || // playheadPosition is position of the playhead while the midroll ad is being played <br />
|-<br />
| <code>mAppSdk.stop();</code> || // Call stop after midroll occurs<br />
|-<br />
| rowspan="3" | Content (End of stream) || <code>mAppSdk.loadMetadata(contentMetaDataObject);</code> || // contentMetadataObject contains the JSON metadata for the content being played<br />
|-<br />
| <code>mAppSdk.setPlayheadPosition(playheadPosition);</code> || // position is position of the playhead while the content is being played<br />
|-<br />
| <code>mAppSdk.stop();</code> || // Always call stop irrespective of postroll is followed or not<br />
|-<br />
| End of Stream || <code>mAppSdk.end();</code> || // Call end() at the end of content<br />
|-<br />
| rowspan="3" | Postroll || <code>mAppSdk.loadMetadata(postrollMetaDataObject);</code> || // postrollMetadataObject contains the JSON metadata for the postroll ad<br />
|-<br />
| <code>mAppSdk.setPlayheadPosition(playheadPosition);</code> || // position is position of the playhead while the postroll ad is being played<br />
|-<br />
| <code>mAppSdk.stop();</code> || // Call stop after postroll occurs<br />
|}<br />
<br />
<blockquote>Note: Each Ad playhead should reset or begin from 0 at ad start. When content has resumed following an ad break, playhead position must continue from where previous content segment was left off.</blockquote><br />
<br />
== Sequence of Calls ==<br />
=== play ===<br />
Use [[DCR_Video_APP_SDK#play|play]] to pass the channel descriptor information through channelName parameter when the user taps the '''Play''' button on the player.<br />
{{ExampleCode|<br />
|Objective C = <syntaxhighlight lang="objective-c"> [nielsenAppApi play:(JSONObject channelInfo)];</syntaxhighlight><br />
|Swift = <syntaxhighlight lang="swift">nielsenAppApi?.play(JSONObject channelInfo);</syntaxhighlight><br />
}}<br />
<br />
=== loadMetadata ===<br />
{{ExampleCode|<br />
|Objective C = <syntaxhighlight lang="objective-c">[nielsenApi loadMetadata:(data)];</syntaxhighlight><br />
|Swift = <syntaxhighlight lang="swift">self.nielsenAppApi?.loadMetadata(contentMetadata)</syntaxhighlight><br />
}}<br />
<br />
=== playheadPosition ===<br />
{{ExampleCode|<br />
|Objective C = <syntaxhighlight lang="objective-c"><br />
– (void) playheadPosition: (long long) playheadPos<br />
</syntaxhighlight><br />
<br />
==== Sending playheadposition for content ====<br />
<syntaxhighlight lang="objective-c"><br />
-(void) setPlayHeadPosition {<br />
<br />
//Setting play head position<br />
CMTime timeInterval = CMTimeMakeWithSeconds(1, 1);<br />
[player addPeriodicTimeObserverForInterval:(timeInterval) queue:dispatch_get_main_queue() usingBlock:^(CMTime time){<br />
NSTimeInterval seconds = CMTimeGetSeconds(time);<br />
NSInteger intSec = seconds;<br />
<br />
//Sending data dictionary to SDK with updated playHead position.<br />
[nielsenApi playheadPosition:(intSec)];<br />
}];<br />
}<br />
</syntaxhighlight><br />
<br />
|Swift = <syntaxhighlight lang="swift"><br />
//Monitor the Playhead position of the AVPlayer<br />
let timeInterval: CMTime = CMTimeMakeWithSeconds(1.0,10)<br />
self.avPlayerViewController.player?.addPeriodicTimeObserver(forInterval: timeInterval, queue: DispatchQueue.main) {(elapsedTime: CMTime) -> Void in<br />
if self.avPlayerViewController.player!.currentItem?.status == .readyToPlay {<br />
let time : Float64 = self.avPlayerViewController.player!.currentTime().seconds;<br />
let pos = Int64(time);<br />
NSLog("New Elapse Time = \(time)");<br />
self.nielsenAppApi?.playheadPosition(pos);<br />
}<br />
}<br />
}<br />
</syntaxhighlight><br />
}}<br />
<br />
=== stop ===<br />
{{ExampleCode|<br />
|Objective C = <syntaxhighlight lang="objective-c">[nielsenApi stop];</syntaxhighlight><br />
|Swift = <syntaxhighlight lang="swift">nielsenApi.stop()</syntaxhighlight><br />
}}<br />
<br />
=== end ===<br />
When content stop is initiated and content cannot be resumed from the same position (it can only be restarted from the beginning of stream).<br />
{{ExampleCode|<br />
|Objective C = <syntaxhighlight lang="objective-c">[nielsenApi end];</syntaxhighlight><br />
|Swift = <syntaxhighlight lang="swift">nielsenApi.end()</syntaxhighlight><br />
}}<br />
<br />
== Handling Foreground and Background states ==<br />
For iOS, background/foreground detection is handled by the app lifecylce APIs which are provided by [https://developer.apple.com/library/content/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/TheAppLifeCycle/TheAppLifeCycle.html Apple:]<br />
<br />
Foreground/Background state measurement is a requirement of Nielsen AppSDK implementation which is especially crucial for static measurement.<br />
<br />
== Interruptions during playback ==<br />
As part of integrating Nielsen App SDK with the player application, the Audio / Video app developer needs to handle the following possible interruption scenarios:<br />
* Pause / Play<br />
* Network Loss (Wi-Fi / Airplane / Cellular)<br />
* Call Interrupt (SIM or Third party Skype / Hangout call)<br />
* Alarm Interrupt<br />
* Content Buffering<br />
* Device Lock / Unlock (Video players only, not for Audio players)<br />
* App going in the Background/Foreground (Video players only, not for Audio players)<br />
* Channel / Station Change Scenario<br />
* Unplugging of headphone<br />
In case of encountering one of the above interruptions, the player application needs to<br />
* Call [[stop]] immediately (except when content is buffering) and withhold sending playhead position.<br />
* Start sending pings – <code>'loadMetadata'</code> and <code>'playheadPosition'</code> for the new viewing session, once the playback resumes.<br />
Please see the [https://engineeringportal.nielsen.com/docs/Digital_Measurement_Interruption_Scenarios Interruption Scenarios Page] for more details<br />
<br />
== Pre-Certification Checklists ==<br />
After the application is ready to be sent for Nielsen Certification, please go through the [[Digital Pre-Certification Checklist App SDK]] and ensure the app behaves as expected, before submitting to Nielsen.<br />
<br />
{{Template:iOS_Privacy_and_Opt-Out}}<br />
<br />
== AirPlay ==<br />
To implement OTT measurement, report OTT changes to the SDK using public API interface: [[updateOTT]]<br />
<br />
In order to detect AirPlay and mirroring changes we use AVAudioSessionPortDescription properties that are different on different iOS versions. We found that on iOS versions 8 - 10 <code>AVAudioSessionPortDescription</code> has the following values:<br><br />
<code><br />
AirPlay: type = AirPlay; name = Apple TV 4K; UID = DC:56:E7:53:72:85-airplay <br><br />
Mirroring: type = AirPlay; name = Apple TV 4K; UID = DC:56:E7:53:72:85-screen<br />
</code><br><br />
<br />
For iOS 11+ some parameters like name and UID have different values:<br><br />
<code><br />
AirPlay: type = AirPlay; name = AirPlay; UID = 0eb63aae-5915-45f1-b0f7-0102a0e50d53 <br><br />
Mirroring: type = AirPlay; name = Apple TV 4K; UID = 4335E8A9-1C0A-4251-9000-28CA5FA2F3CF-192731714653291-screen<br><br />
</code><br />
<br />
To implement OTT measurement, report OTT changes to the SDK using public API interface: [[updateOTT]] <br><br />
The following code snipped is suggested for AirPlay / mirroring detection on iOS devices.<br />
<br />
{{ExampleCode|<br />
|Objective C = <br />
<syntaxhighlight lang="objective-c"><br />
– (void)updateOTT:(id)ottInfo;<br />
</syntaxhighlight><br />
=== Subscribe to AVAudioSessionRouteChangeNotification ===<br />
<syntaxhighlight lang="objective-c"><br />
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleRouteChanged:) name:AVAudioSessionRouteChangeNotification object:nil];<br />
</syntaxhighlight><br />
<br />
=== Handle AVAudioSessionRouteChangeNotification and prepare OTT dictionary:===<br />
<syntaxhighlight lang="objective-c"><br />
- (void)handleRouteChanged:(NSNotification *)notification<br />
{<br />
NSMutableDictionary *ottDict = [NSMutableDictionary dictionaryWithDictionary: @{@"ottStatus": @"0"}];<br />
<br />
AVAudioSession *audioSession = [AVAudioSession sharedInstance];<br />
AVAudioSessionRouteDescription *currentRoute = audioSession.currentRoute;<br />
for (AVAudioSessionPortDescription *outputPort in currentRoute.outputs) {<br />
if ([outputPort.portType isEqualToString:AVAudioSessionPortAirPlay]) {<br />
ottDict[@"ottStatus"] = @"1";<br />
ottDict[@"ottDeviceModel"] = outputPort.portName;<br />
ottDict[@"ottDeviceID"] = outputPort.UID;<br />
<br />
if ([outputPort.portName isEqualToString:@"AirPlay"]) {<br />
ottDict[@"ottDevice"] = @"airplay";<br />
ottDict[@"ottType"] = @"airplay";<br />
}<br />
else {<br />
if ([outputPort.portName containsString:@"Apple TV"]) {<br />
ottDict[@"ottDevice"] = @"appleTV";<br />
}<br />
else {<br />
ottDict[@"ottDevice"] = @"other";<br />
}<br />
<br />
if ([outputPort.UID hasSuffix:@"airplay"]) {<br />
ottDict[@"ottType"] = @"airplay";<br />
}<br />
else if ([outputPort.UID hasSuffix:@"screen"]) {<br />
ottDict[@"ottType"] = @"mirroring";<br />
}<br />
else {<br />
ottDict[@"ottType"] = @"other";<br />
}<br />
}<br />
}<br />
}<br />
<br />
// report OTT status update to Nielsen SDK<br />
[self reportOTTWithDict:ottDict];<br />
}<br />
</syntaxhighlight><br />
<br />
=== Report OTT update to the Nielsen SDK ===<br />
<syntaxhighlight lang="objective-c"><br />
- (void)reportOTTWithDict:(NSDictionary *)ottDict<br />
{<br />
[self.nielsenSDK updateOTT:ottDict];<br />
}<br />
</syntaxhighlight><br />
<br />
|Swift = <br />
<syntaxhighlight lang="swift"><br />
nielsenSdk.updateOTT(currentStatus)<br />
</syntaxhighlight><br />
<br />
=== Subscribe to AVAudioSessionRouteChangeNotification === <br />
<syntaxhighlight lang="swift"><br />
NotificationCenter.default.addObserver(self, selector: #selector(handleRouteChanged(_:)), name: NSNotification.Name.AVAudioSessionRouteChange, object: nil)<br />
</syntaxhighlight><br />
<br />
=== Handle AVAudioSessionRouteChangeNotification and prepare OTT dictionary:===<br />
<br />
<syntaxhighlight lang ="Swift"><br />
func handleRouteChanged(_ notification: Notification) {<br />
var currentStatus: [String: String] = ["ottStatus": "0"]<br />
<br />
let session = AVAudioSession.sharedInstance()<br />
let currentRoute = session.currentRoute<br />
for outputPort in currentRoute.outputs {<br />
if outputPort.portType == AVAudioSessionPortAirPlay {<br />
currentStatus["ottStatus"] = "1"<br />
currentStatus["ottDeviceModel"] = outputPort.portName<br />
currentStatus["ottDeviceID"] = outputPort.uid<br />
<br />
if outputPort.portName == "AirPlay" {<br />
currentStatus["ottDevice"] = "airplay"<br />
currentStatus["ottType"] = "airplay"<br />
}<br />
else {<br />
if outputPort.portName.contains("Apple TV") {<br />
currentStatus["ottDevice"] = "appleTV"<br />
}<br />
else {<br />
currentStatus["ottDevice"] = "other"<br />
}<br />
<br />
if outputPort.uid.hasSuffix("airplay") {<br />
currentStatus["ottType"] = "airplay"<br />
}<br />
else if outputPort.uid.hasSuffix("screen") {<br />
currentStatus["ottType"] = "mirroring"<br />
}<br />
else {<br />
currentStatus["ottType"] = "other"<br />
}<br />
}<br />
}<br />
}<br />
<br />
// report OTT status update to Nielsen SDK<br />
self.reportOTTUpdate(currentStatus)<br />
}<br />
</syntaxhighlight><br />
<br />
=== Report OTT update to the Nielsen SDK===<br />
<syntaxhighlight lang ="Swift"><br />
func reportOTTUpdate(_ ottDict: [String: String]) {<br />
if let nielsenSdk = self.nielsenAppApi {<br />
nielsenSdk.updateOTT(currentStatus)<br />
}<br />
}<br />
</syntaxhighlight><br />
}}<br />
<br />
== Going Live ==<br />
Following Nielsen testing, users need to make one update to the initialization call to ensure that the site is being measured properly.<br />
<br />
# '''Debug Logging''': Disable logging by deleting <code>{nol_sdkDebug: 'DEBUG'}</code> from initialization call.<br />
<br/><br />
'''Note''': before going live you have to inform Nielsen team - this is necessary, because Nielsen team has to adjust internal configuration parameter to enable data collection. Without that notification no data will be collected and no data will be reported.<br />
<br />
== Removing Simulators ==<br />
<br />
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. Here is an example Shell script that could be added as a Run Script phase in the application.<br />
<br />
<br />
<syntaxhighlight lang='bash'><br />
<br />
APP_PATH="${TARGET_BUILD_DIR}/${WRAPPER_NAME}"<br />
<br />
# This script loops through the frameworks embedded in the application and<br />
# removes unused architectures.<br />
find "$APP_PATH" -name '*.framework' -type d | while read -r FRAMEWORK<br />
do<br />
FRAMEWORK_EXECUTABLE_NAME=$(defaults read "$FRAMEWORK/Info.plist" CFBundleExecutable)<br />
FRAMEWORK_EXECUTABLE_PATH="$FRAMEWORK/$FRAMEWORK_EXECUTABLE_NAME"<br />
echo "Executable is $FRAMEWORK_EXECUTABLE_PATH"<br />
<br />
EXTRACTED_ARCHS=()<br />
<br />
for ARCH in $ARCHS<br />
do<br />
echo "Extracting $ARCH from $FRAMEWORK_EXECUTABLE_NAME"<br />
lipo -extract "$ARCH" "$FRAMEWORK_EXECUTABLE_PATH" -o "$FRAMEWORK_EXECUTABLE_PATH-$ARCH"<br />
EXTRACTED_ARCHS+=("$FRAMEWORK_EXECUTABLE_PATH-$ARCH")<br />
done<br />
<br />
echo "Merging extracted architectures: ${ARCHS}"<br />
lipo -o "$FRAMEWORK_EXECUTABLE_PATH-merged" -create "${EXTRACTED_ARCHS[@]}"<br />
rm "${EXTRACTED_ARCHS[@]}"<br />
<br />
echo "Replacing original executable with thinned version"<br />
rm "$FRAMEWORK_EXECUTABLE_PATH"<br />
mv "$FRAMEWORK_EXECUTABLE_PATH-merged" "$FRAMEWORK_EXECUTABLE_PATH"<br />
<br />
done<br />
</syntaxhighlight><br />
<br />
== Sample Applications ==<br />
The below sample applications have been designed to show the Simplified API's functionality and are broken into two distinct categories:<br />
* '''Basic''' - To show the functionality of the Nielsen Simplified API using a standard no-frills player.<br />
** [[Swift Basic Sample|Swift 4.0 Sample]]<br />
** [[Objective-c Basic example|Objective-C Sample]]<br />
** [[Android Basic example|Android Studio Example]]<br />
<br />
* '''Advanced''' - Nielsen Simplified API integrated into a custom video player.<br />
** [https://engineeringportal.nielsen.com/docs/Special:Downloads Swift 4.0 Sample]<br />
** [https://engineeringportal.nielsen.com/docs/Special:Downloads Objective-C Sample]<br />
** [https://engineeringportal.nielsen.com/docs/Special:Downloads Java/Android Studio Sample]</div>AlexGutierrezhttps://engineeringportal.nielsen.com//w/index.php?title=DCR_Video_%26_Static_Cloud_API&diff=3442DCR Video & Static Cloud API2019-02-15T16:28:06Z<p>AlexGutierrez: Changes to playhead description</p>
<hr />
<div>{{Breadcrumb|}} {{Breadcrumb|Digital}} {{Breadcrumb|DCR & DTVR}} {{CurrentBreadcrumb}}<br />
[[Category:Digital]]<br />
<br />
This guide shows you how to integrate the Nielsen Cloud API to enable Digital Content Ratings (DCR) measurement on your over-the-top (OTT) Apps.<br />
*For Roku Apps, please see [[DCR Video & Static Roku Cloud API]]<br />
*For Mobile Apps, please see [[DCR Video & Static Mobile Cloud API]]<br />
<br />
==Prerequisites==<br />
To get started, you will need a Nielsen App ID. The App ID is a unique ID assigned to your app. This will be provided to you upon starting the integration.<br />
<br />
<syntaxhighlight lang="javascript">XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX</syntaxhighlight><br />
<br />
==Integration==<br />
We will cover the steps for constructing the Cloud API Calls.<br />
<br />
===Request Overview===<br />
<br />
====URL Structure====<br />
<br />
The Cloud API Calls are HTTP GET Requests with the URL structure:<br />
<br />
<syntaxhighlight lang="javascript">[endpoint]/[appid]/[sessionID]/a?b=[payload]</syntaxhighlight><br />
<br />
The URL includes the following components:<br />
<br />
*<code>[endpoint]</code>: location of data collection environment<br />
*<code>[appid]</code>: provided App ID<br />
*<code>[sessionID]</code>: unique value for each user session<br />
*<code>[payload]</code>: metadata and events<br />
<br />
====Endpoint====<br />
<br />
There are endpoints for testing and production:<br />
<br />
*Testing: <code>https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/</code><br />
*Production: <code>https://cloudapi.imrworldwide.com/nmapi/v2/</code><br />
<br />
During testing, all calls should be pointed to the testing endpoint. We will review the update to the production endpoint during the Go Live section of this guide.<br />
<br />
====URL Example====<br />
As you move through the integration steps, you can reference the below URL structure with the expanded payload:<br />
<br />
<syntaxhighlight lang="javascript"><br />
https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=<br />
{<br />
"devInfo": [deviceInfo],<br />
"metadata": {<br />
"static": [static_metadata],<br />
"content": [content_metadata],<br />
"ad": [ad metadata]<br />
},<br />
"event": [event],<br />
"position": [playhead_position],<br />
"type": [asset type],<br />
"utc": [Unix time in ms]<br />
}<br />
</syntaxhighlight><br />
<br />
===Create Session ID===<br />
A unique Session ID must be created upon app launch and provided in the URL. This will allow measurement to occur for the entire duration that a user is within the app.<br />
<br />
A Session ID needs to be completely unique so it is recommended to use a version 4 UUID or another method of your choosing to guarantee there are no repeats.<br />
<br />
Upon exiting the app, the session will need to be terminated using the delete event. Sessions will automatically expire after 30 minutes of cloud inactivity.<br />
<br />
===Define URL Structure===<br />
Define the URL structure using your provided <code>[appid]</code> and a unique <code>[sessionID]</code>.<br />
<br />
<syntaxhighlight lang="javascript">https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=[payload]</syntaxhighlight><br />
<br />
===Configure Payload===<br />
<br />
All Cloud API requests must contain the following payload data:<br />
<br />
*''devInfo'': device and app info<br />
*''metadata'': asset metadata<br />
*''event metadata'': type of event<br />
<br />
The payload can be passed through key-values using the Nielsen reserved keys. The specific keys and descriptions are highlighted in the tables included in this section.<br />
<br />
'''Payload Example'''<br />
<br />
The example below should be referenced when following the steps for configuring the request payload.<br />
<br />
<syntaxhighlight lang="javascript"><br />
// 3.1 Configure Payload: devInfo <br />
payload = {<br />
"devInfo": {<br />
"devId": "AD-ID", <br />
"apn": "AppName",<br />
"apv": "1.0",<br />
"uoo": "false"<br />
},<br />
<br />
// 3.2 Configure Payload: metadata<br />
"metadata": {<br />
"static": {}, // object for measuring static content<br />
"content": { // object for measuring video content<br />
"type": "content", // "content" for video<br />
"assetid": "VIDEO-ID123", // unique ID for video<br />
"isfullepisode": "y", // full episode flag<br />
"program": "Program Name", // program name<br />
"title": "Episode Title S3 - EP1", // episode name<br />
"length": "1800", // content duration in seconds<br />
"segB": "Custom Segment B", // custom segment<br />
"segC": "Custom Segment C", // custom segment<br />
"crossId1": "Standard Episode ID", // episode ID<br />
"crossId2": "Content Originator ID", // content orginator (required for distributors)<br />
"airdate": "20161013 20:00:00", // airdate<br />
"adloadtype": "2" //ad load flag<br />
"hasAds": "1", // content contains ads = 1 / no ads = 0<br />
"progen": "CV" // program genre abbreviation<br />
},<br />
"ad": {<br />
"type": "preroll", // type of ad<br />
"assetid": "AD-ID123" // unique ID for ad<br />
}<br />
},<br />
<br />
// 3.3 Configure Payload: events<br />
"event": "playhead", //event name<br />
"position": "300", // position in seconds<br />
"type": "content", //"content" or "ad"<br />
"utc": "1456448742000" //unix timestamp in milliseconds <br />
}<br />
</syntaxhighlight><br />
<br />
=====Configure Payload: devInfo=====<br />
An object <code>"devInfo"</code> will need to be created to capture App and Device information.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| devId || unique ID to identify user (e.g. Advertising ID, Roku Device ID) || custom || Yes<br />
|-<br />
| apn || app name || custom || Yes<br />
|-<br />
| apv || app build version || custom || Yes<br />
|-<br />
| uoo || device opt-out status || <code>"true"</code> or <code>"false"</code> || Yes<br />
|-<br />
|}<br />
<br />
'''Example devInfo Object'''<br />
<syntaxhighlight lang="javascript"><br />
// create devInfo object<br />
"devInfo": {<br />
"devId": "AD-ID",<br />
"apn": "AppName",<br />
"apv": "1.0",<br />
"uoo": "false"<br />
},<br />
</syntaxhighlight><br />
<br />
==== 3.2 Configure Payload: metadata ====<br />
Asset metadata can be passed through <code>"metadata"</code>. There are two asset types: <code>"content"</code> for video and <code>"ad"</code> for ads. The metadata received for each asset is used for classification and reporting.<br />
<br />
You will need to set up <code>"metadata"</code> objects for <code>"content"</code> and <code>"ad"</code> with the required Nielsen keys as shown in the sample code below.<br />
<br />
===== Content Metadata =====<br />
Content metadata should remain constant throughout the entirety of an episode/clip including when ads play.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| type || type of asset || <code>"content"</code> || Yes<br />
|-<br />
| assetid || unique ID assigned to asset || custom || Yes<br />
|-<br />
| program ||name of program (25 character limit) || custom || Yes<br />
|-<br />
| title ||name of program (25 character limit) || custom || Yes<br />
|-<br />
| length || length of content in seconds || <code>seconds</code> (0 for live stream) || Yes<br />
|-<br />
| segB || custom segment B || custom || <br />
|-<br />
| segC || custom segment C || custom || <br />
|-<br />
| airdate || the airdate in the linear TV || YYYYMMDD HH24:MI:SS || Yes<br />
|-<br />
| isfullepisode || full episode flag || <code>"y"</code>- full episode, <code>"n"</code>- non full episode || Yes<br />
|-<br />
| crossId1 || standard episode ID || custom || Yes<br />
|-<br />
| crossId2 || content originator (only required for distributors) || Nielsen ||<br />
|-<br />
| adloadtype || type of ad load:<br />
<code>"1"</code> Linear – matches TV ad load<br />
<br />
<code>"2"</code> Dynamic – Dynamic Ad Insertion (DAI)<br />
|| <code>"2"</code> - DCR measures content with dynamic ads || Yes<br />
|-<br />
| hasAds || ads indicator<br />
<code>"1"</code>: ads included<br />
<br />
<code>"0"</code>: ads not included<br />
|| <code>"1"</code> or <code>"0"</code> || Yes<br />
|-<br />
| progen || Genre (required only for non-TV originated content). See [[DCR OTT Genre List]] for accepted values || <code>"CV"</code> for Comedy Variety || Required for non-TV originated content<br />
|}<br />
<br />
<br />
'''Example Content Object'''<br />
<syntaxhighlight lang='json'>// create content object<br />
"content": {<br />
"type": "content",<br />
"assetid": "VIDEO-ID123",<br />
"isfullepisode": "y",<br />
"program": "Program Name",<br />
"title": "Episode Title S3 - EP1",<br />
"length": "1800",<br />
"segB": "Custom Segment B",<br />
"segC": "Custom Segment C",<br />
"crossId1": "Standard Episode ID",<br />
"crossId2": "Content Originator ID",<br />
"airdate": "20161013 20:00:00",<br />
"adloadtype": "2",<br />
"hasAds": "1", <br />
"progen": "CV"<br />
}</syntaxhighlight><br />
<br />
===== Ad Metadata =====<br />
The ad metadata should be passed for each individual ad.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| type || type of ad || <code>"preroll"</code>, <code>"midroll"</code>, or <code>"postroll"</code> || Yes<br />
|-<br />
| assetid || unique ID assigned to ad || custom || Yes<br />
|}<br />
<br />
===== Example Ad Object =====<br />
<syntaxhighlight lang="javascript"><br />
// create ad object<br />
"ad": {<br />
"type": "preroll",<br />
"assetid": "AD-ID123"<br />
}<br />
</syntaxhighlight><br />
<br />
=== Configure Payload: Events ===<br />
<br />
The last part of the payload is for enabling events so content is measured correctly when viewed. The events and required parameters are included below.<br />
<br />
==== Event Types ====<br />
<br />
The available events are:<br />
{| class="wikitable"<br />
|-<br />
! Event !! Description<br />
|-<br />
| <code>"playhead"</code> || Playhead position in seconds. Must be passed as a whole number every 10 seconds. The final playhead position should also be sent before an asset has changed to properly capture full duration. Playhead is used to handle pause and scrubbing. When content is paused, stop passing playhead position.<br />
|-<br />
| <code>"complete"</code> || Complete event must be sent when the content has completed full playback or if a user has initiated a Stop. Before calling the complete event, a final playhead update with the final position is required to be sent to receive full duration credit. Complete does not need to be called if a user transitions videos (e.g. channel change or selecting a new VOD asset) before the video completes.<br />
|-<br />
| <code>"delete"</code> || The delete event should be sent when the application is exited and the viewing session is completed. After 30 minutes of inactivity, the session will expire automatically. All creditable duration will be summarized for all asset types when delete occurs (content and ads). Note that the delete event may not be feasible on certain platforms that do not offer the necessary callbacks to trigger this event.<br />
|}<br />
<br />
===== Event Parameters =====<br />
<br />
The following parameters need to be passed when calling events:<br />
<br />
{| class="wikitable"<br />
|-<br />
! Parameter !! Description !! Value !! Required<br />
|-<br />
| <code>"event"</code> || event type || <code>"playhead"</code>, <code>"complete"</code>, or <code>"delete"</code> || Yes<br />
|-<br />
| <code>"position"</code> || playhead position in seconds || <code>"300"</code> || Yes<br />
|-<br />
| <code>"type"</code> || asset type || <code>"content"</code>, <code>"ad"</code> || Yes<br />
|-<br />
| <code>"utc"</code> || Unix timestamp in milliseconds. Must be passed every 10 seconds. || <code>"1472760000000"</code> || Yes<br />
|}<br />
<br />
===== Example Event =====<br />
You can call events by passing values in the required parameters:<br />
<br />
<syntaxhighlight lang="javascript"><br />
"devInfo": [deviceInfo],<br />
"metadata": {<br />
"static": [static metadata],<br />
"content": [content metadata],<br />
"ad": [ad metadata]<br />
},<br />
// Event Parameters<br />
"event": [event], // event name<br />
"position": [playheadPosition], //position in seconds<br />
"type": [asset type], // values are "content" or "ad"<br />
"utc": "1472760000000" //unix timestamp in milliseconds<br />
}<br />
</syntaxhighlight><br />
<br />
'''Note:''' The full payload including "devInfo" and "metadata" must be populated in each event request.<br />
<br />
===== Sample Event Lifecycle =====<br />
The sample event lifecycle can be used as a reference for identifying the order for calling events and values to pass.<br />
<br />
<syntaxhighlight lang="javascript"><br />
// Start of Session: session ID created when App is opened<br />
<br />
// Preroll<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
// Content<br />
Content Playhead {"event": "playhead", "position": "0", "type": "content", "utc": "1472760000000"} <br />
<br />
// Midroll<br />
Midroll Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
// Content resumes at 15 minutes<br />
Content Playhead {"event": "playhead", "position": "900", "type": "content", "utc": "1472760000000"} <br />
<br />
// Content completes at 30 minutes<br />
Complete {"event": "complete", "position": "1800", "type": "content", "utc": "1472760000000"} <br />
<br />
// Postroll<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
//End of Session: The delete event should be called when the App is exited. The values for position and type not required to be passed.<br />
Delete { "event": "delete", "position": "", "type": "", "utc": "1472760000000"} <br />
</syntaxhighlight><br />
<br />
<br />
'''Sample Event Lifecycle - Detailed Storyline'''<br />
This detailed event sequence provides additional insight for the correct events to call when handling certain playback scenarios.<br />
<syntaxhighlight lang='javascript'>// SESSION STARTS<br />
// Start of Session: session ID created when App is opened<br />
<br />
// PREROLL<br />
// Preroll Start - Start each Ad with a position of "0", resetting to '0' for each Ad, and Ad break.<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
// Preroll Stop - End each Ad with the final position of the Ad.<br />
Ad Playhead {"event": "playhead", "position": "15", "type": "ad", "utc": "1472761500000"}<br />
<br />
// CONTENT <br />
// Content Start - Start new content streams with a position of "0" incrementing the position every 10 seconds.<br />
Content Playhead {"event": "playhead", "position": "0", "type": "content", "utc": "1472761500000"} <br />
<br />
// Content Stop Before Ad Break - Send a playhead update including the current content positon before an Ad break.<br />
Content Playhead {"event": "playhead", "position": "299", "type": "content", "utc": "1472787400000"}<br />
<br />
// MIDROLL<br />
// Midroll Start - Start each Ad with a position of "0", resetting to '0' for each Ad, and Ad break.<br />
Midroll Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472787500000"} <br />
<br />
// Midroll Stop - End each Ad with the final position of the Ad.<br />
Ad Playhead {"event": "playhead", "position": "60", "type": "ad", "utc": "1472793500000"}<br />
<br />
// CONTENT<br />
// Content resumes at 5 minutes - Send playhead update with the current resumed position, and begin incrimenting the positon every 10 seconds.<br />
Content Playhead {"event": "playhead", "position": "300", "type": "content", "utc": "1472799500000"} <br />
<br />
// Content completes at 10:12 - Make sure to send in the playhead event with the final content position before sending the complete event.<br />
Final Content Playhead {"event": "playhead", "position": "612", "type": "content", "utc": "1472830700000"} <br />
<br />
Complete {"event": "complete", "position": "612", "type": "content", "utc": "1472830800000"} <br />
<br />
// POSTROLL<br />
// Postroll Start - Start each Ad with a position of "0", resetting to '0' for each Ad, and Ad break.<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472830900000"} <br />
<br />
// Postroll Stop - End each Ad with the final position of the Ad.<br />
Ad Playhead {"event": "playhead", "position": "45", "type": "ad", "utc": "1472835300000"}<br />
<br />
// SESSION ENDS<br />
<br />
//End of Session: The delete event should be called when the App is exited. The values for position and type not required to be passed.<br />
Delete { "event": "delete", "position": "", "type": "", "utc": "1472835400000"} </syntaxhighlight><br />
<br />
<br />
=====Handling Playhead=====<br />
Calling <code>"playhead"</code> is critical for accurate duration crediting. You can reference the below guidance to determine the correct playhead position to pass depending on the playback scenario.<br />
<br />
'''Playhead: General'''<br />
* Playhead position must start at 0 for each new asset, and be passed at least every 10 seconds.<br />
* Final postion must be sent at the end of content or an ad<br />
<br />
'''Playhead: Ads'''<br />
* The final position must be sent when switching from content to ad, or ad to content.<br />
* Each ad playhead position should be 0 at ad start.<br />
* For Ad Pods, playhead must be called, and reset to 0 for each individual ad. <br />
* The last content position before an Ad should be sent before switching to Ads.<br />
* When content has resumed following an ad break, the playhead position update must continue where the previous content segment left off.<br />
<br />
'''Playhead: User Actions'''<br />
* Upon user scrubbing, the current position must be sent before a user scrubs, and the new position should be sent where the user lands, and begin sending in the 10 second updates thereafter.<br />
* On pause, send the current position and then discontinue sending playhead event updates.<br />
* If a user exits a stream early, the last current position must be sent in a playhead update to receive accurate duration.<br />
<br />
===== Interruption Scenarios =====<br />
<br />
As part of configuring events, you will need to handle all possible interruption scenarios such as:<br />
<br />
*Wi-Fi OFF / ON<br />
*App going Background / Foreground (Video players only, not for Audio players)<br />
*App Crash or Exit<br />
<br />
When playback is interrupted, the app needs to send delete immediately.<br />
<br />
Once playback resumes, a new session will need to be created with a unique session ID. All of the required metadata and events will need to be sent.<br />
<br />
'''Note:''' The session will automatically timeout after 30 minutes of inactivity.<br />
<br />
=== Example Request ===<br />
<br />
Now that we walked through the Cloud API integration steps, your requests should have the following components: Session ID, App ID, and Payload. You can reference the example below when your reviewing your integration.<br />
<br />
<syntaxhighlight lang="javascript"><br />
// 1. Create Session ID<br />
sessionID = dfc7dc6a-66a7-4705-9fba-adaaf7e3d5e0 // Example sessionID created using a UUID Generator<br />
<br />
// 2. Define URL Structure with App ID and Session ID<br />
sessionURL = https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=<br />
<br />
// 3. Configure Payload<br />
// 3.1 Configure Payload: devInfo <br />
payload = {<br />
"devInfo": {<br />
"devId": "AD-ID", <br />
"apn": "AppName",<br />
"apv": "1.0",<br />
"uoo": "false"<br />
},<br />
<br />
// 3.2 Configure Payload: metadata<br />
"metadata": {<br />
"static": {}, // object for measuring static content<br />
"content": { // object for measuring video content<br />
"type": "content", // "content" for video<br />
"assetid": "VIDEO-ID123", // unique ID for video<br />
"isfullepisode": "y", // full episode flag<br />
"program": "Program Name", // program name<br />
"title": "Episode Title S3 - EP1", // episode name<br />
"length": "1800", // content duration in seconds<br />
"segB": "Custom Segment B", // custom segment<br />
"segC": "Custom Segment C", // custom segment<br />
"crossId1": "Standard Episode ID", // episode ID<br />
"crossId2": "Content Originator ID", // content orginator (required for distributors)<br />
"airdate": "20161013 20:00:00", // airdate<br />
"adloadtype": "2", // ad load flag<br />
"hasAds": "1", // content contains ads = 1 / no ads = 0<br />
"progen": "CV" // program genre abbreviation<br />
},<br />
"ad": {<br />
"type": "preroll", // type of ad<br />
"assetid": "AD-ID123" // unique ID for ad<br />
}<br />
},<br />
<br />
// 3.3 Configure Payload: events<br />
"event": "playhead", //event name<br />
"position": "300", // position in seconds<br />
"type": "content", //"content" or "ad"<br />
"utc": "1456448742000" //unix timestamp in milliseconds <br />
}<br />
<br />
// Append payload to URL<br />
var image = new Image()<br />
image.onerror = function() {<br />
// wait and send again<br />
}<br />
(new Image).src = sessionURL+encodeURI(JSON.stringify(payload));<br />
</syntaxhighlight><br />
<br />
==== Enable Debug Logging ====<br />
<br />
Now that you have set up the Cloud API requests, you can enable debug logging to validate your integration. Enabling debug logging is required for Nielsen certification.<br />
<br />
==== GET Request ====<br />
<br />
Display GET Request to console using a name to identify each event (e.g. playhead).<br />
<br />
<syntaxhighlight lang="javascript"><br />
console.log("Event", image); <br />
</syntaxhighlight><br />
<br />
==== Payload ====<br />
<br />
Output payload to identify required metadata and events.<br />
<br />
<syntaxhighlight lang="javascript"><br />
console.log("Event Payload", payload); <br />
</syntaxhighlight><br />
<br />
==== HTTP Response Code ====<br />
<br />
Confirm request was completed by viewing HTTP response code.<br />
<br />
<syntaxhighlight lang="javascript"><br />
code = msg.GetResponseCode();<br />
console.log("Response Code", code); <br />
</syntaxhighlight><br />
<br />
You can reference the HTTP Response Code table when reviewing your requests:<br />
<br />
{| class="wikitable"<br />
|-<br />
! Status Code !! Status Text !! Description<br />
|-<br />
| <code>200</code> || OK || request received<br />
|-<br />
| <code>403</code> || Forbidden || invalid App ID<br />
|-<br />
| <code>404</code> || Not Found || JSON issue<br />
|}<br />
<br />
== Opt-Out ==<br />
Your app must provide a means for the user to Opt-Out, or Opt-In to Nielsen Measurement. This requirement can be fulfilled by checking the device OS for the user's setting of "Limit Ad Tracking" or similar option. If the device offers "Limit Ad Tracking" settings, you should set <code>uoo=true</code> or <code>uoo=false</code> depending on the user's privacy setting. Also, ensure that the <code>devId</code> is set to a blank value if the user elects to opt-out.<br />
<br />
If the device does not have OS-level "Do Not Track" settings, you can implement opt-out by creating an Opt-Out/Opt-In button, toggle switch, or slider within the app "Settings", or "About" section to allow the user selection.<br />
<br />
[[File:Nielsen Opt-Out.png|link=]]<br />
<br />
You will need to store the User Opt-Out (uoo) status, so that it can be retrieved and populated in the <code>"devInfo"</code> metadata which will be sent in every Cloud API event (playhead, complete, & delete). <br />
<br />
{| class="wikitable"<br />
|-<br />
! uoo Key !! Description !! Values<br />
|-<br />
| uoo || Device is Opted-In to Nielsen Measurement (Recommended Default Setting) || <code>"false"</code><br />
|-<br />
| uoo || Device is Opted-Out of Nielsen Measurement || <code>"true"</code><br />
|}<br />
<br />
===== devInfo Opt-In JSON Payload Example =====<br />
<br />
<syntaxhighlight lang="javascript"><br />
"devInfo": {<br />
"apn": "Cloud API Sample App",<br />
"apv": "1",<br />
"devId": "7be25cf9-8c40-5cc2-871e-19bf41940288",<br />
"uoo": "false"<br />
},<br />
</syntaxhighlight><br />
<br />
===== devInfo Opt-Out JSON Payload Example =====<br />
<br />
<syntaxhighlight lang="javascript"><br />
"devInfo": {<br />
"apn": "Cloud API Sample App",<br />
"apv": "1",<br />
"devId": "", //devId must be blank when a user elects to Opt-Out.<br />
"uoo": "true"<br />
},<br />
</syntaxhighlight><br />
<br />
===== Privacy Information Template To Include In Opt-Out Screen =====<br />
<br />
<br />
Additionally, all applications must display the Nielsen privacy policy within their application, typically in the Settings/About screen of your application. The Nielsen privacy policy text is listed below.<br />
<br />
<blockquote><br />
'''''ABOUT NIELSEN MEASUREMENT'''''<br />
<br />
Television and the way we watch it have come a long way since Nielsen began measuring TV audiences in 1950. Today, the ability to watch our favorite shows at any time and on multiple devices amplifies the need for exceptionally adept and flexible audience measurement capabilities.<br />
<br />
Consumers are changing with the times, and the same goes for us. As technology continues to evolve and media companies try new ways to attract viewers, understanding what consumers are watching — and what they're watching on — is more important than ever. Today, viewing video is a personal and mobile experience — anytime and anywhere. Our capabilities provide relevant metrics that are necessary to inform successful marketing and programming and drive continued growth. As a global information and measurement leader, we are committed to protecting the privacy and security of the data we collect, process and use. While our digital measurement products are not used to identify you in any way, they help us and our clients measure and analyze how consumers engage with media across online, mobile and emerging technologies, and offer insights into consumer behavior.<br />
<br />
'''''YOUR CHOICES'''''<br />
<br />
Nielsen believes that you should have a choice about whether to contribute to our research and insights. To opt out or opt in to Nielsen measurement, please toggle your "Limit Ad Tracking" (or similar setting) on your device, or make the appropriate selection within the application's settings. If you have this app on more than one device, you will need to opt out of this app on each device. To learn more about our digital measurement products and your choices in regard to them, please visit http://www.nielsen.com/digitalprivacy.<br />
<br />
'''''Disclosure Template'''''<br />
<br />
Additionally, you must update the App description information from the App Distribution Store with the Nielsen Measurement disclosure below:<br />
<br />
This app features Nielsen's proprietary measurement software which will allow you to contribute to market research, like Nielsen's TV Ratings. Please see http://www.nielsen.com/digitalprivacy for more information.<br />
</blockquote><br />
<br />
== Testing ==<br />
Before providing an app build to Nielsen for testing, it is important to run validation checks once you have enabled debug logging.<br />
<br />
=== Payload Validation ===<br />
<br />
Ensure that all of the required payload data is populating while testing several videos. The following areas are critical to measurement:<br />
*devInfo<br />
*Asset metadata for both content, and ads<br />
*Events<br />
*Opt-Out status<br />
<br />
=== Player Events ===<br />
Review event calls:<br />
<br />
==== playhead ====<br />
*Playhead position updates every 10 seconds starting at position '0' for each new asset.<br />
*Final playhead position is sent on content, or ad before switching between assets.<br />
*Content metadata remains constant throughout an episode, or clip play.<br />
*Ad metadata is populated appropriately for each individual ad.<br />
*Playhead position update resumes for content after an ad break, and resets to 0 for each individual ad.<br />
*For scrubbing, last current position should be sent while scrubbing occurs, and the new position should also be sent where the user scrubs to.<br />
*Exiting a stream early should execute the last current position in a playhead update to receive accurate duration.<br />
*Upon pause, the current position should be sent, and playhead updates should stop incrementing until resume play occurs.<br />
<br />
==== complete ====<br />
*Check that the complete event executes upon content complete after the final playhead update is sent<br />
*Do not execute the complete event for ads<br />
<br />
==== delete ====<br />
*Check to see that the delete event occurs upon app exit, if the platform has the necessary exit callback events.<br />
<br />
==== GET Request Format ====<br />
*Ensure that the event payloads are formatted in JSON<br />
*Check to see that each of the Cloud API GET requests are properly encoded<br />
<br />
==== HTTP Response ====<br />
*Make sure that each of the Cloud API Get requests are received by the Nielsen Cloud API properly through use of the HTTP Response Code outputs enabled in console.<br />
<br />
==== Opt-Out ====<br />
*Test the "uoo" key gets populated accurately for both Opt-In and Opt-Out selections by validating the Cloud API events called after the user Opt-Out/Opt-In selection.<br />
*Test that the devId field is populated with a blank value if a user has elected to Opt-Out. For example: "devId": "",<br />
*If the device supports "Limit Ad Tracking" or has device "Opt-Out" settings, test that uoo=true, and that devId is set to a blank value if enabled in the device settings.<br />
<br />
== Go Live ==<br />
After your integration has been certified, you will need to: Change Endpoint and Disable Logging.<br />
<br />
'''Change Endpoint:''' You will need to update to the production endpoint:<br />
<br />
*Testing: <code>https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/</code><br />
*Production: <code>https://cloudapi.imrworldwide.com/nmapi/v2/</code><br />
<br />
Your production URL structure should now be:<br />
<code>https://cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=[payload]</code><br />
<br />
'''Disable Logging:''' You can now disable debug logging</div>AlexGutierrezhttps://engineeringportal.nielsen.com//w/index.php?title=DCR_Video_%26_Static_Cloud_API&diff=3441DCR Video & Static Cloud API2019-02-15T16:24:40Z<p>AlexGutierrez: Clarifying playhead position values</p>
<hr />
<div>{{Breadcrumb|}} {{Breadcrumb|Digital}} {{Breadcrumb|DCR & DTVR}} {{CurrentBreadcrumb}}<br />
[[Category:Digital]]<br />
<br />
This guide shows you how to integrate the Nielsen Cloud API to enable Digital Content Ratings (DCR) measurement on your over-the-top (OTT) Apps.<br />
*For Roku Apps, please see [[DCR Video & Static Roku Cloud API]]<br />
*For Mobile Apps, please see [[DCR Video & Static Mobile Cloud API]]<br />
<br />
==Prerequisites==<br />
To get started, you will need a Nielsen App ID. The App ID is a unique ID assigned to your app. This will be provided to you upon starting the integration.<br />
<br />
<syntaxhighlight lang="javascript">XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX</syntaxhighlight><br />
<br />
==Integration==<br />
We will cover the steps for constructing the Cloud API Calls.<br />
<br />
===Request Overview===<br />
<br />
====URL Structure====<br />
<br />
The Cloud API Calls are HTTP GET Requests with the URL structure:<br />
<br />
<syntaxhighlight lang="javascript">[endpoint]/[appid]/[sessionID]/a?b=[payload]</syntaxhighlight><br />
<br />
The URL includes the following components:<br />
<br />
*<code>[endpoint]</code>: location of data collection environment<br />
*<code>[appid]</code>: provided App ID<br />
*<code>[sessionID]</code>: unique value for each user session<br />
*<code>[payload]</code>: metadata and events<br />
<br />
====Endpoint====<br />
<br />
There are endpoints for testing and production:<br />
<br />
*Testing: <code>https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/</code><br />
*Production: <code>https://cloudapi.imrworldwide.com/nmapi/v2/</code><br />
<br />
During testing, all calls should be pointed to the testing endpoint. We will review the update to the production endpoint during the Go Live section of this guide.<br />
<br />
====URL Example====<br />
As you move through the integration steps, you can reference the below URL structure with the expanded payload:<br />
<br />
<syntaxhighlight lang="javascript"><br />
https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=<br />
{<br />
"devInfo": [deviceInfo],<br />
"metadata": {<br />
"static": [static_metadata],<br />
"content": [content_metadata],<br />
"ad": [ad metadata]<br />
},<br />
"event": [event],<br />
"position": [playhead_position],<br />
"type": [asset type],<br />
"utc": [Unix time in ms]<br />
}<br />
</syntaxhighlight><br />
<br />
===Create Session ID===<br />
A unique Session ID must be created upon app launch and provided in the URL. This will allow measurement to occur for the entire duration that a user is within the app.<br />
<br />
A Session ID needs to be completely unique so it is recommended to use a version 4 UUID or another method of your choosing to guarantee there are no repeats.<br />
<br />
Upon exiting the app, the session will need to be terminated using the delete event. Sessions will automatically expire after 30 minutes of cloud inactivity.<br />
<br />
===Define URL Structure===<br />
Define the URL structure using your provided <code>[appid]</code> and a unique <code>[sessionID]</code>.<br />
<br />
<syntaxhighlight lang="javascript">https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=[payload]</syntaxhighlight><br />
<br />
===Configure Payload===<br />
<br />
All Cloud API requests must contain the following payload data:<br />
<br />
*''devInfo'': device and app info<br />
*''metadata'': asset metadata<br />
*''event metadata'': type of event<br />
<br />
The payload can be passed through key-values using the Nielsen reserved keys. The specific keys and descriptions are highlighted in the tables included in this section.<br />
<br />
'''Payload Example'''<br />
<br />
The example below should be referenced when following the steps for configuring the request payload.<br />
<br />
<syntaxhighlight lang="javascript"><br />
// 3.1 Configure Payload: devInfo <br />
payload = {<br />
"devInfo": {<br />
"devId": "AD-ID", <br />
"apn": "AppName",<br />
"apv": "1.0",<br />
"uoo": "false"<br />
},<br />
<br />
// 3.2 Configure Payload: metadata<br />
"metadata": {<br />
"static": {}, // object for measuring static content<br />
"content": { // object for measuring video content<br />
"type": "content", // "content" for video<br />
"assetid": "VIDEO-ID123", // unique ID for video<br />
"isfullepisode": "y", // full episode flag<br />
"program": "Program Name", // program name<br />
"title": "Episode Title S3 - EP1", // episode name<br />
"length": "1800", // content duration in seconds<br />
"segB": "Custom Segment B", // custom segment<br />
"segC": "Custom Segment C", // custom segment<br />
"crossId1": "Standard Episode ID", // episode ID<br />
"crossId2": "Content Originator ID", // content orginator (required for distributors)<br />
"airdate": "20161013 20:00:00", // airdate<br />
"adloadtype": "2" //ad load flag<br />
"hasAds": "1", // content contains ads = 1 / no ads = 0<br />
"progen": "CV" // program genre abbreviation<br />
},<br />
"ad": {<br />
"type": "preroll", // type of ad<br />
"assetid": "AD-ID123" // unique ID for ad<br />
}<br />
},<br />
<br />
// 3.3 Configure Payload: events<br />
"event": "playhead", //event name<br />
"position": "300", // position in seconds<br />
"type": "content", //"content" or "ad"<br />
"utc": "1456448742000" //unix timestamp in milliseconds <br />
}<br />
</syntaxhighlight><br />
<br />
=====Configure Payload: devInfo=====<br />
An object <code>"devInfo"</code> will need to be created to capture App and Device information.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| devId || unique ID to identify user (e.g. Advertising ID, Roku Device ID) || custom || Yes<br />
|-<br />
| apn || app name || custom || Yes<br />
|-<br />
| apv || app build version || custom || Yes<br />
|-<br />
| uoo || device opt-out status || <code>"true"</code> or <code>"false"</code> || Yes<br />
|-<br />
|}<br />
<br />
'''Example devInfo Object'''<br />
<syntaxhighlight lang="javascript"><br />
// create devInfo object<br />
"devInfo": {<br />
"devId": "AD-ID",<br />
"apn": "AppName",<br />
"apv": "1.0",<br />
"uoo": "false"<br />
},<br />
</syntaxhighlight><br />
<br />
==== 3.2 Configure Payload: metadata ====<br />
Asset metadata can be passed through <code>"metadata"</code>. There are two asset types: <code>"content"</code> for video and <code>"ad"</code> for ads. The metadata received for each asset is used for classification and reporting.<br />
<br />
You will need to set up <code>"metadata"</code> objects for <code>"content"</code> and <code>"ad"</code> with the required Nielsen keys as shown in the sample code below.<br />
<br />
===== Content Metadata =====<br />
Content metadata should remain constant throughout the entirety of an episode/clip including when ads play.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| type || type of asset || <code>"content"</code> || Yes<br />
|-<br />
| assetid || unique ID assigned to asset || custom || Yes<br />
|-<br />
| program ||name of program (25 character limit) || custom || Yes<br />
|-<br />
| title ||name of program (25 character limit) || custom || Yes<br />
|-<br />
| length || length of content in seconds || <code>seconds</code> (0 for live stream) || Yes<br />
|-<br />
| segB || custom segment B || custom || <br />
|-<br />
| segC || custom segment C || custom || <br />
|-<br />
| airdate || the airdate in the linear TV || YYYYMMDD HH24:MI:SS || Yes<br />
|-<br />
| isfullepisode || full episode flag || <code>"y"</code>- full episode, <code>"n"</code>- non full episode || Yes<br />
|-<br />
| crossId1 || standard episode ID || custom || Yes<br />
|-<br />
| crossId2 || content originator (only required for distributors) || Nielsen ||<br />
|-<br />
| adloadtype || type of ad load:<br />
<code>"1"</code> Linear – matches TV ad load<br />
<br />
<code>"2"</code> Dynamic – Dynamic Ad Insertion (DAI)<br />
|| <code>"2"</code> - DCR measures content with dynamic ads || Yes<br />
|-<br />
| hasAds || ads indicator<br />
<code>"1"</code>: ads included<br />
<br />
<code>"0"</code>: ads not included<br />
|| <code>"1"</code> or <code>"0"</code> || Yes<br />
|-<br />
| progen || Genre (required only for non-TV originated content). See [[DCR OTT Genre List]] for accepted values || <code>"CV"</code> for Comedy Variety || Required for non-TV originated content<br />
|}<br />
<br />
<br />
'''Example Content Object'''<br />
<syntaxhighlight lang='json'>// create content object<br />
"content": {<br />
"type": "content",<br />
"assetid": "VIDEO-ID123",<br />
"isfullepisode": "y",<br />
"program": "Program Name",<br />
"title": "Episode Title S3 - EP1",<br />
"length": "1800",<br />
"segB": "Custom Segment B",<br />
"segC": "Custom Segment C",<br />
"crossId1": "Standard Episode ID",<br />
"crossId2": "Content Originator ID",<br />
"airdate": "20161013 20:00:00",<br />
"adloadtype": "2",<br />
"hasAds": "1", <br />
"progen": "CV"<br />
}</syntaxhighlight><br />
<br />
===== Ad Metadata =====<br />
The ad metadata should be passed for each individual ad.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| type || type of ad || <code>"preroll"</code>, <code>"midroll"</code>, or <code>"postroll"</code> || Yes<br />
|-<br />
| assetid || unique ID assigned to ad || custom || Yes<br />
|}<br />
<br />
===== Example Ad Object =====<br />
<syntaxhighlight lang="javascript"><br />
// create ad object<br />
"ad": {<br />
"type": "preroll",<br />
"assetid": "AD-ID123"<br />
}<br />
</syntaxhighlight><br />
<br />
=== Configure Payload: Events ===<br />
<br />
The last part of the payload is for enabling events so content is measured correctly when viewed. The events and required parameters are included below.<br />
<br />
==== Event Types ====<br />
<br />
The available events are:<br />
{| class="wikitable"<br />
|-<br />
! Event !! Description<br />
|-<br />
| <code>"playhead"</code> || Playhead position in seconds. Must be passed as a whole number every 10 seconds. The final playhead position should also be sent before an asset has changed to properly capture full duration. Playhead is used to handle pause and scrubbing. When content is paused, stop passing playhead position.<br />
|-<br />
| <code>"complete"</code> || Complete event must be sent when the content has completed full playback or if a user has initiated a Stop. Before calling the complete event, a final playhead update with the final position is required to be sent to receive full duration credit. Complete does not need to be called if a user transitions videos (e.g. channel change or selecting a new VOD asset) before the video completes.<br />
|-<br />
| <code>"delete"</code> || The delete event should be sent when the application is exited and the viewing session is completed. After 30 minutes of inactivity, the session will expire automatically. All creditable duration will be summarized for all asset types when delete occurs (content and ads). Note that the delete event may not be feasible on certain platforms that do not offer the necessary callbacks to trigger this event.<br />
|}<br />
<br />
===== Event Parameters =====<br />
<br />
The following parameters need to be passed when calling events:<br />
<br />
{| class="wikitable"<br />
|-<br />
! Parameter !! Description !! Value !! Required<br />
|-<br />
| <code>"event"</code> || event type || <code>"playhead"</code>, <code>"complete"</code>, or <code>"delete"</code> || Yes<br />
|-<br />
| <code>"position"</code> || creditable position || playhead position in seconds, as reported by the video player. For live content, Unix timestamp (epoch time) can be used. || Yes<br />
|-<br />
| <code>"type"</code> || asset type || <code>"content"</code>, <code>"ad"</code> || Yes<br />
|-<br />
| <code>"utc"</code> || Unix timestamp in milliseconds. Must be passed every 10 seconds. || <code>"1472760000000"</code> || Yes<br />
|}<br />
<br />
===== Example Event =====<br />
You can call events by passing values in the required parameters:<br />
<br />
<syntaxhighlight lang="javascript"><br />
"devInfo": [deviceInfo],<br />
"metadata": {<br />
"static": [static metadata],<br />
"content": [content metadata],<br />
"ad": [ad metadata]<br />
},<br />
// Event Parameters<br />
"event": [event], // event name<br />
"position": [playheadPosition], //position in seconds<br />
"type": [asset type], // values are "content" or "ad"<br />
"utc": "1472760000000" //unix timestamp in milliseconds<br />
}<br />
</syntaxhighlight><br />
<br />
'''Note:''' The full payload including "devInfo" and "metadata" must be populated in each event request.<br />
<br />
===== Sample Event Lifecycle =====<br />
The sample event lifecycle can be used as a reference for identifying the order for calling events and values to pass.<br />
<br />
<syntaxhighlight lang="javascript"><br />
// Start of Session: session ID created when App is opened<br />
<br />
// Preroll<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
// Content<br />
Content Playhead {"event": "playhead", "position": "0", "type": "content", "utc": "1472760000000"} <br />
<br />
// Midroll<br />
Midroll Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
// Content resumes at 15 minutes<br />
Content Playhead {"event": "playhead", "position": "900", "type": "content", "utc": "1472760000000"} <br />
<br />
// Content completes at 30 minutes<br />
Complete {"event": "complete", "position": "1800", "type": "content", "utc": "1472760000000"} <br />
<br />
// Postroll<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
//End of Session: The delete event should be called when the App is exited. The values for position and type not required to be passed.<br />
Delete { "event": "delete", "position": "", "type": "", "utc": "1472760000000"} <br />
</syntaxhighlight><br />
<br />
<br />
'''Sample Event Lifecycle - Detailed Storyline'''<br />
This detailed event sequence provides additional insight for the correct events to call when handling certain playback scenarios.<br />
<syntaxhighlight lang='javascript'>// SESSION STARTS<br />
// Start of Session: session ID created when App is opened<br />
<br />
// PREROLL<br />
// Preroll Start - Start each Ad with a position of "0", resetting to '0' for each Ad, and Ad break.<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
// Preroll Stop - End each Ad with the final position of the Ad.<br />
Ad Playhead {"event": "playhead", "position": "15", "type": "ad", "utc": "1472761500000"}<br />
<br />
// CONTENT <br />
// Content Start - Start new content streams with a position of "0" incrementing the position every 10 seconds.<br />
Content Playhead {"event": "playhead", "position": "0", "type": "content", "utc": "1472761500000"} <br />
<br />
// Content Stop Before Ad Break - Send a playhead update including the current content positon before an Ad break.<br />
Content Playhead {"event": "playhead", "position": "299", "type": "content", "utc": "1472787400000"}<br />
<br />
// MIDROLL<br />
// Midroll Start - Start each Ad with a position of "0", resetting to '0' for each Ad, and Ad break.<br />
Midroll Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472787500000"} <br />
<br />
// Midroll Stop - End each Ad with the final position of the Ad.<br />
Ad Playhead {"event": "playhead", "position": "60", "type": "ad", "utc": "1472793500000"}<br />
<br />
// CONTENT<br />
// Content resumes at 5 minutes - Send playhead update with the current resumed position, and begin incrimenting the positon every 10 seconds.<br />
Content Playhead {"event": "playhead", "position": "300", "type": "content", "utc": "1472799500000"} <br />
<br />
// Content completes at 10:12 - Make sure to send in the playhead event with the final content position before sending the complete event.<br />
Final Content Playhead {"event": "playhead", "position": "612", "type": "content", "utc": "1472830700000"} <br />
<br />
Complete {"event": "complete", "position": "612", "type": "content", "utc": "1472830800000"} <br />
<br />
// POSTROLL<br />
// Postroll Start - Start each Ad with a position of "0", resetting to '0' for each Ad, and Ad break.<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472830900000"} <br />
<br />
// Postroll Stop - End each Ad with the final position of the Ad.<br />
Ad Playhead {"event": "playhead", "position": "45", "type": "ad", "utc": "1472835300000"}<br />
<br />
// SESSION ENDS<br />
<br />
//End of Session: The delete event should be called when the App is exited. The values for position and type not required to be passed.<br />
Delete { "event": "delete", "position": "", "type": "", "utc": "1472835400000"} </syntaxhighlight><br />
<br />
<br />
=====Handling Playhead=====<br />
Calling <code>"playhead"</code> is critical for accurate duration crediting. You can reference the below guidance to determine the correct playhead position to pass depending on the playback scenario.<br />
<br />
'''Playhead: General'''<br />
* Playhead position must start at 0 for each new asset, and be passed at least every 10 seconds.<br />
* Final postion must be sent at the end of content or an ad<br />
<br />
'''Playhead: Ads'''<br />
* The final position must be sent when switching from content to ad, or ad to content.<br />
* Each ad playhead position should be 0 at ad start.<br />
* For Ad Pods, playhead must be called, and reset to 0 for each individual ad. <br />
* The last content position before an Ad should be sent before switching to Ads.<br />
* When content has resumed following an ad break, the playhead position update must continue where the previous content segment left off.<br />
<br />
'''Playhead: User Actions'''<br />
* Upon user scrubbing, the current position must be sent before a user scrubs, and the new position should be sent where the user lands, and begin sending in the 10 second updates thereafter.<br />
* On pause, send the current position and then discontinue sending playhead event updates.<br />
* If a user exits a stream early, the last current position must be sent in a playhead update to receive accurate duration.<br />
<br />
===== Interruption Scenarios =====<br />
<br />
As part of configuring events, you will need to handle all possible interruption scenarios such as:<br />
<br />
*Wi-Fi OFF / ON<br />
*App going Background / Foreground (Video players only, not for Audio players)<br />
*App Crash or Exit<br />
<br />
When playback is interrupted, the app needs to send delete immediately.<br />
<br />
Once playback resumes, a new session will need to be created with a unique session ID. All of the required metadata and events will need to be sent.<br />
<br />
'''Note:''' The session will automatically timeout after 30 minutes of inactivity.<br />
<br />
=== Example Request ===<br />
<br />
Now that we walked through the Cloud API integration steps, your requests should have the following components: Session ID, App ID, and Payload. You can reference the example below when your reviewing your integration.<br />
<br />
<syntaxhighlight lang="javascript"><br />
// 1. Create Session ID<br />
sessionID = dfc7dc6a-66a7-4705-9fba-adaaf7e3d5e0 // Example sessionID created using a UUID Generator<br />
<br />
// 2. Define URL Structure with App ID and Session ID<br />
sessionURL = https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=<br />
<br />
// 3. Configure Payload<br />
// 3.1 Configure Payload: devInfo <br />
payload = {<br />
"devInfo": {<br />
"devId": "AD-ID", <br />
"apn": "AppName",<br />
"apv": "1.0",<br />
"uoo": "false"<br />
},<br />
<br />
// 3.2 Configure Payload: metadata<br />
"metadata": {<br />
"static": {}, // object for measuring static content<br />
"content": { // object for measuring video content<br />
"type": "content", // "content" for video<br />
"assetid": "VIDEO-ID123", // unique ID for video<br />
"isfullepisode": "y", // full episode flag<br />
"program": "Program Name", // program name<br />
"title": "Episode Title S3 - EP1", // episode name<br />
"length": "1800", // content duration in seconds<br />
"segB": "Custom Segment B", // custom segment<br />
"segC": "Custom Segment C", // custom segment<br />
"crossId1": "Standard Episode ID", // episode ID<br />
"crossId2": "Content Originator ID", // content orginator (required for distributors)<br />
"airdate": "20161013 20:00:00", // airdate<br />
"adloadtype": "2", // ad load flag<br />
"hasAds": "1", // content contains ads = 1 / no ads = 0<br />
"progen": "CV" // program genre abbreviation<br />
},<br />
"ad": {<br />
"type": "preroll", // type of ad<br />
"assetid": "AD-ID123" // unique ID for ad<br />
}<br />
},<br />
<br />
// 3.3 Configure Payload: events<br />
"event": "playhead", //event name<br />
"position": "300", // position in seconds<br />
"type": "content", //"content" or "ad"<br />
"utc": "1456448742000" //unix timestamp in milliseconds <br />
}<br />
<br />
// Append payload to URL<br />
var image = new Image()<br />
image.onerror = function() {<br />
// wait and send again<br />
}<br />
(new Image).src = sessionURL+encodeURI(JSON.stringify(payload));<br />
</syntaxhighlight><br />
<br />
==== Enable Debug Logging ====<br />
<br />
Now that you have set up the Cloud API requests, you can enable debug logging to validate your integration. Enabling debug logging is required for Nielsen certification.<br />
<br />
==== GET Request ====<br />
<br />
Display GET Request to console using a name to identify each event (e.g. playhead).<br />
<br />
<syntaxhighlight lang="javascript"><br />
console.log("Event", image); <br />
</syntaxhighlight><br />
<br />
==== Payload ====<br />
<br />
Output payload to identify required metadata and events.<br />
<br />
<syntaxhighlight lang="javascript"><br />
console.log("Event Payload", payload); <br />
</syntaxhighlight><br />
<br />
==== HTTP Response Code ====<br />
<br />
Confirm request was completed by viewing HTTP response code.<br />
<br />
<syntaxhighlight lang="javascript"><br />
code = msg.GetResponseCode();<br />
console.log("Response Code", code); <br />
</syntaxhighlight><br />
<br />
You can reference the HTTP Response Code table when reviewing your requests:<br />
<br />
{| class="wikitable"<br />
|-<br />
! Status Code !! Status Text !! Description<br />
|-<br />
| <code>200</code> || OK || request received<br />
|-<br />
| <code>403</code> || Forbidden || invalid App ID<br />
|-<br />
| <code>404</code> || Not Found || JSON issue<br />
|}<br />
<br />
== Opt-Out ==<br />
Your app must provide a means for the user to Opt-Out, or Opt-In to Nielsen Measurement. This requirement can be fulfilled by checking the device OS for the user's setting of "Limit Ad Tracking" or similar option. If the device offers "Limit Ad Tracking" settings, you should set <code>uoo=true</code> or <code>uoo=false</code> depending on the user's privacy setting. Also, ensure that the <code>devId</code> is set to a blank value if the user elects to opt-out.<br />
<br />
If the device does not have OS-level "Do Not Track" settings, you can implement opt-out by creating an Opt-Out/Opt-In button, toggle switch, or slider within the app "Settings", or "About" section to allow the user selection.<br />
<br />
[[File:Nielsen Opt-Out.png|link=]]<br />
<br />
You will need to store the User Opt-Out (uoo) status, so that it can be retrieved and populated in the <code>"devInfo"</code> metadata which will be sent in every Cloud API event (playhead, complete, & delete). <br />
<br />
{| class="wikitable"<br />
|-<br />
! uoo Key !! Description !! Values<br />
|-<br />
| uoo || Device is Opted-In to Nielsen Measurement (Recommended Default Setting) || <code>"false"</code><br />
|-<br />
| uoo || Device is Opted-Out of Nielsen Measurement || <code>"true"</code><br />
|}<br />
<br />
===== devInfo Opt-In JSON Payload Example =====<br />
<br />
<syntaxhighlight lang="javascript"><br />
"devInfo": {<br />
"apn": "Cloud API Sample App",<br />
"apv": "1",<br />
"devId": "7be25cf9-8c40-5cc2-871e-19bf41940288",<br />
"uoo": "false"<br />
},<br />
</syntaxhighlight><br />
<br />
===== devInfo Opt-Out JSON Payload Example =====<br />
<br />
<syntaxhighlight lang="javascript"><br />
"devInfo": {<br />
"apn": "Cloud API Sample App",<br />
"apv": "1",<br />
"devId": "", //devId must be blank when a user elects to Opt-Out.<br />
"uoo": "true"<br />
},<br />
</syntaxhighlight><br />
<br />
===== Privacy Information Template To Include In Opt-Out Screen =====<br />
<br />
<br />
Additionally, all applications must display the Nielsen privacy policy within their application, typically in the Settings/About screen of your application. The Nielsen privacy policy text is listed below.<br />
<br />
<blockquote><br />
'''''ABOUT NIELSEN MEASUREMENT'''''<br />
<br />
Television and the way we watch it have come a long way since Nielsen began measuring TV audiences in 1950. Today, the ability to watch our favorite shows at any time and on multiple devices amplifies the need for exceptionally adept and flexible audience measurement capabilities.<br />
<br />
Consumers are changing with the times, and the same goes for us. As technology continues to evolve and media companies try new ways to attract viewers, understanding what consumers are watching — and what they're watching on — is more important than ever. Today, viewing video is a personal and mobile experience — anytime and anywhere. Our capabilities provide relevant metrics that are necessary to inform successful marketing and programming and drive continued growth. As a global information and measurement leader, we are committed to protecting the privacy and security of the data we collect, process and use. While our digital measurement products are not used to identify you in any way, they help us and our clients measure and analyze how consumers engage with media across online, mobile and emerging technologies, and offer insights into consumer behavior.<br />
<br />
'''''YOUR CHOICES'''''<br />
<br />
Nielsen believes that you should have a choice about whether to contribute to our research and insights. To opt out or opt in to Nielsen measurement, please toggle your "Limit Ad Tracking" (or similar setting) on your device, or make the appropriate selection within the application's settings. If you have this app on more than one device, you will need to opt out of this app on each device. To learn more about our digital measurement products and your choices in regard to them, please visit http://www.nielsen.com/digitalprivacy.<br />
<br />
'''''Disclosure Template'''''<br />
<br />
Additionally, you must update the App description information from the App Distribution Store with the Nielsen Measurement disclosure below:<br />
<br />
This app features Nielsen's proprietary measurement software which will allow you to contribute to market research, like Nielsen's TV Ratings. Please see http://www.nielsen.com/digitalprivacy for more information.<br />
</blockquote><br />
<br />
== Testing ==<br />
Before providing an app build to Nielsen for testing, it is important to run validation checks once you have enabled debug logging.<br />
<br />
=== Payload Validation ===<br />
<br />
Ensure that all of the required payload data is populating while testing several videos. The following areas are critical to measurement:<br />
*devInfo<br />
*Asset metadata for both content, and ads<br />
*Events<br />
*Opt-Out status<br />
<br />
=== Player Events ===<br />
Review event calls:<br />
<br />
==== playhead ====<br />
*Playhead position updates every 10 seconds starting at position '0' for each new asset.<br />
*Final playhead position is sent on content, or ad before switching between assets.<br />
*Content metadata remains constant throughout an episode, or clip play.<br />
*Ad metadata is populated appropriately for each individual ad.<br />
*Playhead position update resumes for content after an ad break, and resets to 0 for each individual ad.<br />
*For scrubbing, last current position should be sent while scrubbing occurs, and the new position should also be sent where the user scrubs to.<br />
*Exiting a stream early should execute the last current position in a playhead update to receive accurate duration.<br />
*Upon pause, the current position should be sent, and playhead updates should stop incrementing until resume play occurs.<br />
<br />
==== complete ====<br />
*Check that the complete event executes upon content complete after the final playhead update is sent<br />
*Do not execute the complete event for ads<br />
<br />
==== delete ====<br />
*Check to see that the delete event occurs upon app exit, if the platform has the necessary exit callback events.<br />
<br />
==== GET Request Format ====<br />
*Ensure that the event payloads are formatted in JSON<br />
*Check to see that each of the Cloud API GET requests are properly encoded<br />
<br />
==== HTTP Response ====<br />
*Make sure that each of the Cloud API Get requests are received by the Nielsen Cloud API properly through use of the HTTP Response Code outputs enabled in console.<br />
<br />
==== Opt-Out ====<br />
*Test the "uoo" key gets populated accurately for both Opt-In and Opt-Out selections by validating the Cloud API events called after the user Opt-Out/Opt-In selection.<br />
*Test that the devId field is populated with a blank value if a user has elected to Opt-Out. For example: "devId": "",<br />
*If the device supports "Limit Ad Tracking" or has device "Opt-Out" settings, test that uoo=true, and that devId is set to a blank value if enabled in the device settings.<br />
<br />
== Go Live ==<br />
After your integration has been certified, you will need to: Change Endpoint and Disable Logging.<br />
<br />
'''Change Endpoint:''' You will need to update to the production endpoint:<br />
<br />
*Testing: <code>https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/</code><br />
*Production: <code>https://cloudapi.imrworldwide.com/nmapi/v2/</code><br />
<br />
Your production URL structure should now be:<br />
<code>https://cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=[payload]</code><br />
<br />
'''Disable Logging:''' You can now disable debug logging</div>AlexGutierrezhttps://engineeringportal.nielsen.com//w/index.php?title=DCR_Video_%26_Static_Cloud_API&diff=3366DCR Video & Static Cloud API2019-02-01T20:53:48Z<p>AlexGutierrez: </p>
<hr />
<div>{{Breadcrumb|}} {{Breadcrumb|Digital}} {{Breadcrumb|DCR & DTVR}} {{CurrentBreadcrumb}}<br />
[[Category:Digital]]<br />
<br />
This guide shows you how to integrate the Nielsen Cloud API to enable Digital Content Ratings (DCR) measurement on your over-the-top (OTT) Apps.<br />
*For Roku Apps, please see [[DCR Video & Static Roku Cloud API]]<br />
*For Mobile Apps, please see [[DCR Video & Static Mobile Cloud API]]<br />
<br />
==Prerequisites==<br />
To get started, you will need a Nielsen App ID. The App ID is a unique ID assigned to your app. This will be provided to you upon starting the integration.<br />
<br />
<syntaxhighlight lang="javascript">XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX</syntaxhighlight><br />
<br />
==Integration==<br />
We will cover the steps for constructing the Cloud API Calls.<br />
<br />
===Request Overview===<br />
<br />
====URL Structure====<br />
<br />
The Cloud API Calls are HTTP GET Requests with the URL structure:<br />
<br />
<syntaxhighlight lang="javascript">[endpoint]/[appid]/[sessionID]/a?b=[payload]</syntaxhighlight><br />
<br />
The URL includes the following components:<br />
<br />
*<code>[endpoint]</code>: location of data collection environment<br />
*<code>[appid]</code>: provided App ID<br />
*<code>[sessionID]</code>: unique value for each user session<br />
*<code>[payload]</code>: metadata and events<br />
<br />
====Endpoint====<br />
<br />
There are endpoints for testing and production:<br />
<br />
*Testing: <code>https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/</code><br />
*Production: <code>https://cloudapi.imrworldwide.com/nmapi/v2/</code><br />
<br />
During testing, all calls should be pointed to the testing endpoint. We will review the update to the production endpoint during the Go Live section of this guide.<br />
<br />
====URL Example====<br />
As you move through the integration steps, you can reference the below URL structure with the expanded payload:<br />
<br />
<syntaxhighlight lang="javascript"><br />
https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=<br />
{<br />
"devInfo": [deviceInfo],<br />
"metadata": {<br />
"static": [static_metadata],<br />
"content": [content_metadata],<br />
"ad": [ad metadata]<br />
},<br />
"event": [event],<br />
"position": [playhead_position],<br />
"type": [asset type],<br />
"utc": [Unix time in ms]<br />
}<br />
</syntaxhighlight><br />
<br />
===Create Session ID===<br />
A unique Session ID must be created upon app launch and provided in the URL. This will allow measurement to occur for the entire duration that a user is within the app.<br />
<br />
A Session ID needs to be completely unique so it is recommended to use a version 4 UUID or another method of your choosing to guarantee there are no repeats.<br />
<br />
Upon exiting the app, the session will need to be terminated using the delete event. Sessions will automatically expire after 30 minutes of cloud inactivity.<br />
<br />
===Define URL Structure===<br />
Define the URL structure using your provided <code>[appid]</code> and a unique <code>[sessionID]</code>.<br />
<br />
<syntaxhighlight lang="javascript">https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=[payload]</syntaxhighlight><br />
<br />
===Configure Payload===<br />
<br />
All Cloud API requests must contain the following payload data:<br />
<br />
*''devInfo'': device and app info<br />
*''metadata'': asset metadata<br />
*''event metadata'': type of event<br />
<br />
The payload can be passed through key-values using the Nielsen reserved keys. The specific keys and descriptions are highlighted in the tables included in this section.<br />
<br />
'''Payload Example'''<br />
<br />
The example below should be referenced when following the steps for configuring the request payload.<br />
<br />
<syntaxhighlight lang="javascript"><br />
// 3.1 Configure Payload: devInfo <br />
payload = {<br />
"devInfo": {<br />
"devId": "AD-ID", <br />
"apn": "AppName",<br />
"apv": "1.0",<br />
"uoo": "false"<br />
},<br />
<br />
// 3.2 Configure Payload: metadata<br />
"metadata": {<br />
"static": {}, // object for measuring static content<br />
"content": { // object for measuring video content<br />
"type": "content", // "content" for video<br />
"assetid": "VIDEO-ID123", // unique ID for video<br />
"isfullepisode": "y", // full episode flag<br />
"program": "Program Name", // program name<br />
"title": "Episode Title S3 - EP1", // episode name<br />
"length": "1800", // content duration in seconds<br />
"segB": "Custom Segment B", // custom segment<br />
"segC": "Custom Segment C", // custom segment<br />
"crossId1": "Standard Episode ID", // episode ID<br />
"crossId2": "Content Originator ID", // content orginator (required for distributors)<br />
"airdate": "20161013 20:00:00", // airdate<br />
"adloadtype": "2" //ad load flag<br />
"hasAds": "1", // content contains ads = 1 / no ads = 0<br />
"progen": "CV" // program genre abbreviation<br />
},<br />
"ad": {<br />
"type": "preroll", // type of ad<br />
"assetid": "AD-ID123" // unique ID for ad<br />
}<br />
},<br />
<br />
// 3.3 Configure Payload: events<br />
"event": "playhead", //event name<br />
"position": "300", // position in seconds<br />
"type": "content", //"content" or "ad"<br />
"utc": "1456448742000" //unix timestamp in milliseconds <br />
}<br />
</syntaxhighlight><br />
<br />
=====Configure Payload: devInfo=====<br />
An object <code>"devInfo"</code> will need to be created to capture App and Device information.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| devId || unique ID to identify user (e.g. Advertising ID, Roku Device ID) || custom || Yes<br />
|-<br />
| apn || app name || custom || Yes<br />
|-<br />
| apv || app build version || custom || Yes<br />
|-<br />
| uoo || device opt-out status || <code>"true"</code> or <code>"false"</code> || Yes<br />
|-<br />
|}<br />
<br />
'''Example devInfo Object'''<br />
<syntaxhighlight lang="javascript"><br />
// create devInfo object<br />
"devInfo": {<br />
"devId": "AD-ID",<br />
"apn": "AppName",<br />
"apv": "1.0",<br />
"uoo": "false"<br />
},<br />
</syntaxhighlight><br />
<br />
==== 3.2 Configure Payload: metadata ====<br />
Asset metadata can be passed through <code>"metadata"</code>. There are two asset types: <code>"content"</code> for video and <code>"ad"</code> for ads. The metadata received for each asset is used for classification and reporting.<br />
<br />
You will need to set up <code>"metadata"</code> objects for <code>"content"</code> and <code>"ad"</code> with the required Nielsen keys as shown in the sample code below.<br />
<br />
===== Content Metadata =====<br />
Content metadata should remain constant throughout the entirety of an episode/clip including when ads play.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| type || type of asset || <code>"content"</code> || Yes<br />
|-<br />
| assetid || unique ID assigned to asset || custom || Yes<br />
|-<br />
| program ||name of program (25 character limit) || custom || Yes<br />
|-<br />
| title ||name of program (25 character limit) || custom || Yes<br />
|-<br />
| length || length of content in seconds || <code>seconds</code> (0 for live stream) || Yes<br />
|-<br />
| segB || custom segment B || custom || <br />
|-<br />
| segC || custom segment C || custom || <br />
|-<br />
| airdate || the airdate in the linear TV || YYYYMMDD HH24:MI:SS || Yes<br />
|-<br />
| isfullepisode || full episode flag || <code>"y"</code>- full episode, <code>"n"</code>- non full episode || Yes<br />
|-<br />
| crossId1 || standard episode ID || custom || Yes<br />
|-<br />
| crossId2 || content originator (only required for distributors) || Nielsen ||<br />
|-<br />
| adloadtype || type of ad load:<br />
<code>"1"</code> Linear – matches TV ad load<br />
<br />
<code>"2"</code> Dynamic – Dynamic Ad Insertion (DAI)<br />
|| <code>"2"</code> - DCR measures content with dynamic ads || Yes<br />
|-<br />
| hasAds || ads indicator<br />
<code>"1"</code>: ads included<br />
<br />
<code>"0"</code>: ads not included<br />
|| <code>"1"</code> or <code>"0"</code> || Yes<br />
|-<br />
| progen || Genre (required only for non-TV originated content). See [[DCR OTT Genre List]] for accepted values || <code>"CV"</code> for Comedy Variety || Required for non-TV originated content<br />
|}<br />
<br />
<br />
'''Example Content Object'''<br />
<syntaxhighlight lang='json'>// create content object<br />
"content": {<br />
"type": "content",<br />
"assetid": "VIDEO-ID123",<br />
"isfullepisode": "y",<br />
"program": "Program Name",<br />
"title": "Episode Title S3 - EP1",<br />
"length": "1800",<br />
"segB": "Custom Segment B",<br />
"segC": "Custom Segment C",<br />
"crossId1": "Standard Episode ID",<br />
"crossId2": "Content Originator ID",<br />
"airdate": "20161013 20:00:00",<br />
"adloadtype": "2",<br />
"hasAds": "1", <br />
"progen": "CV"<br />
}</syntaxhighlight><br />
<br />
===== Ad Metadata =====<br />
The ad metadata should be passed for each individual ad.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| type || type of ad || <code>"preroll"</code>, <code>"midroll"</code>, or <code>"postroll"</code> || Yes<br />
|-<br />
| assetid || unique ID assigned to ad || custom || Yes<br />
|}<br />
<br />
===== Example Ad Object =====<br />
<syntaxhighlight lang="javascript"><br />
// create ad object<br />
"ad": {<br />
"type": "preroll",<br />
"assetid": "AD-ID123"<br />
}<br />
</syntaxhighlight><br />
<br />
=== Configure Payload: Events ===<br />
<br />
The last part of the payload is for enabling events so content is measured correctly when viewed. The events and required parameters are included below.<br />
<br />
==== Event Types ====<br />
<br />
The available events are:<br />
{| class="wikitable"<br />
|-<br />
! Event !! Description<br />
|-<br />
| <code>"playhead"</code> || Playhead position in seconds. Must be passed as a whole number every 10 seconds. The final playhead position should also be sent before an asset has changed to properly capture full duration. Playhead is used to handle pause and scrubbing. When content is paused, stop passing playhead position.<br />
|-<br />
| <code>"complete"</code> || Complete event must be sent when the content has completed full playback or if a user has initiated a Stop. Before calling the complete event, a final playhead update with the final position is required to be sent to receive full duration credit. Complete does not need to be called if a user transitions videos (e.g. channel change or selecting a new VOD asset) before the video completes.<br />
|-<br />
| <code>"delete"</code> || The delete event should be sent when the application is exited and the viewing session is completed. After 30 minutes of inactivity, the session will expire automatically. All creditable duration will be summarized for all asset types when delete occurs (content and ads). Note that the delete event may not be feasible on certain platforms that do not offer the necessary callbacks to trigger this event.<br />
|}<br />
<br />
===== Event Parameters =====<br />
<br />
The following parameters need to be passed when calling events:<br />
<br />
{| class="wikitable"<br />
|-<br />
! Parameter !! Description !! Value !! Required<br />
|-<br />
| <code>"event"</code> || event type || <code>"playhead"</code>, <code>"complete"</code>, or <code>"delete"</code> || Yes<br />
|-<br />
| <code>"position"</code> || creditable position || playhead position in Unix timestamp (seconds since Jan-1-1970 UTC) for livestream || Yes<br />
|-<br />
| <code>"type"</code> || asset type || <code>"content"</code>, <code>"ad"</code> || Yes<br />
|-<br />
| <code>"utc"</code> || Unix timestamp in milliseconds. Must be passed every 10 seconds. || <code>"1472760000000"</code> || Yes<br />
|}<br />
<br />
===== Example Event =====<br />
You can call events by passing values in the required parameters:<br />
<br />
<syntaxhighlight lang="javascript"><br />
"devInfo": [deviceInfo],<br />
"metadata": {<br />
"static": [static metadata],<br />
"content": [content metadata],<br />
"ad": [ad metadata]<br />
},<br />
// Event Parameters<br />
"event": [event], // event name<br />
"position": [playheadPosition], //position in seconds<br />
"type": [asset type], // values are "content" or "ad"<br />
"utc": "1472760000000" //unix timestamp in milliseconds<br />
}<br />
</syntaxhighlight><br />
<br />
'''Note:''' The full payload including "devInfo" and "metadata" must be populated in each event request.<br />
<br />
===== Sample Event Lifecycle =====<br />
The sample event lifecycle can be used as a reference for identifying the order for calling events and values to pass.<br />
<br />
<syntaxhighlight lang="javascript"><br />
// Start of Session: session ID created when App is opened<br />
<br />
// Preroll<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
// Content<br />
Content Playhead {"event": "playhead", "position": "0", "type": "content", "utc": "1472760000000"} <br />
<br />
// Midroll<br />
Midroll Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
// Content resumes at 15 minutes<br />
Content Playhead {"event": "playhead", "position": "900", "type": "content", "utc": "1472760000000"} <br />
<br />
// Content completes at 30 minutes<br />
Complete {"event": "complete", "position": "1800", "type": "content", "utc": "1472760000000"} <br />
<br />
// Postroll<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
//End of Session: The delete event should be called when the App is exited. The values for position and type not required to be passed.<br />
Delete { "event": "delete", "position": "", "type": "", "utc": "1472760000000"} <br />
</syntaxhighlight><br />
<br />
<br />
'''Sample Event Lifecycle - Detailed Storyline'''<br />
This detailed event sequence provides additional insight for the correct events to call when handling certain playback scenarios.<br />
<syntaxhighlight lang='javascript'>// SESSION STARTS<br />
// Start of Session: session ID created when App is opened<br />
<br />
// PREROLL<br />
// Preroll Start - Start each Ad with a position of "0", resetting to '0' for each Ad, and Ad break.<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
// Preroll Stop - End each Ad with the final position of the Ad.<br />
Ad Playhead {"event": "playhead", "position": "15", "type": "ad", "utc": "1472761500000"}<br />
<br />
// CONTENT <br />
// Content Start - Start new content streams with a position of "0" incrementing the position every 10 seconds.<br />
Content Playhead {"event": "playhead", "position": "0", "type": "content", "utc": "1472761500000"} <br />
<br />
// Content Stop Before Ad Break - Send a playhead update including the current content positon before an Ad break.<br />
Content Playhead {"event": "playhead", "position": "299", "type": "content", "utc": "1472787400000"}<br />
<br />
// MIDROLL<br />
// Midroll Start - Start each Ad with a position of "0", resetting to '0' for each Ad, and Ad break.<br />
Midroll Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472787500000"} <br />
<br />
// Midroll Stop - End each Ad with the final position of the Ad.<br />
Ad Playhead {"event": "playhead", "position": "60", "type": "ad", "utc": "1472793500000"}<br />
<br />
// CONTENT<br />
// Content resumes at 5 minutes - Send playhead update with the current resumed position, and begin incrimenting the positon every 10 seconds.<br />
Content Playhead {"event": "playhead", "position": "300", "type": "content", "utc": "1472799500000"} <br />
<br />
// Content completes at 10:12 - Make sure to send in the playhead event with the final content position before sending the complete event.<br />
Final Content Playhead {"event": "playhead", "position": "612", "type": "content", "utc": "1472830700000"} <br />
<br />
Complete {"event": "complete", "position": "612", "type": "content", "utc": "1472830800000"} <br />
<br />
// POSTROLL<br />
// Postroll Start - Start each Ad with a position of "0", resetting to '0' for each Ad, and Ad break.<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472830900000"} <br />
<br />
// Postroll Stop - End each Ad with the final position of the Ad.<br />
Ad Playhead {"event": "playhead", "position": "45", "type": "ad", "utc": "1472835300000"}<br />
<br />
// SESSION ENDS<br />
<br />
//End of Session: The delete event should be called when the App is exited. The values for position and type not required to be passed.<br />
Delete { "event": "delete", "position": "", "type": "", "utc": "1472835400000"} </syntaxhighlight><br />
<br />
<br />
=====Handling Playhead=====<br />
Calling <code>"playhead"</code> is critical for accurate duration crediting. You can reference the below guidance to determine the correct playhead position to pass depending on the playback scenario.<br />
<br />
'''Playhead: General'''<br />
* Playhead position must start at 0 for each new asset, and be passed at least every 10 seconds.<br />
* Final postion must be sent at the end of content or an ad<br />
<br />
'''Playhead: Ads'''<br />
* The final position must be sent when switching from content to ad, or ad to content.<br />
* Each ad playhead position should be 0 at ad start.<br />
* For Ad Pods, playhead must be called, and reset to 0 for each individual ad. <br />
* The last content position before an Ad should be sent before switching to Ads.<br />
* When content has resumed following an ad break, the playhead position update must continue where the previous content segment left off.<br />
<br />
'''Playhead: User Actions'''<br />
* Upon user scrubbing, the current position must be sent before a user scrubs, and the new position should be sent where the user lands, and begin sending in the 10 second updates thereafter.<br />
* On pause, send the current position and then discontinue sending playhead event updates.<br />
* If a user exits a stream early, the last current position must be sent in a playhead update to receive accurate duration.<br />
<br />
===== Interruption Scenarios =====<br />
<br />
As part of configuring events, you will need to handle all possible interruption scenarios such as:<br />
<br />
*Wi-Fi OFF / ON<br />
*App going Background / Foreground (Video players only, not for Audio players)<br />
*App Crash or Exit<br />
<br />
When playback is interrupted, the app needs to send delete immediately.<br />
<br />
Once playback resumes, a new session will need to be created with a unique session ID. All of the required metadata and events will need to be sent.<br />
<br />
'''Note:''' The session will automatically timeout after 30 minutes of inactivity.<br />
<br />
=== Example Request ===<br />
<br />
Now that we walked through the Cloud API integration steps, your requests should have the following components: Session ID, App ID, and Payload. You can reference the example below when your reviewing your integration.<br />
<br />
<syntaxhighlight lang="javascript"><br />
// 1. Create Session ID<br />
sessionID = dfc7dc6a-66a7-4705-9fba-adaaf7e3d5e0 // Example sessionID created using a UUID Generator<br />
<br />
// 2. Define URL Structure with App ID and Session ID<br />
sessionURL = https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=<br />
<br />
// 3. Configure Payload<br />
// 3.1 Configure Payload: devInfo <br />
payload = {<br />
"devInfo": {<br />
"devId": "AD-ID", <br />
"apn": "AppName",<br />
"apv": "1.0",<br />
"uoo": "false"<br />
},<br />
<br />
// 3.2 Configure Payload: metadata<br />
"metadata": {<br />
"static": {}, // object for measuring static content<br />
"content": { // object for measuring video content<br />
"type": "content", // "content" for video<br />
"assetid": "VIDEO-ID123", // unique ID for video<br />
"isfullepisode": "y", // full episode flag<br />
"program": "Program Name", // program name<br />
"title": "Episode Title S3 - EP1", // episode name<br />
"length": "1800", // content duration in seconds<br />
"segB": "Custom Segment B", // custom segment<br />
"segC": "Custom Segment C", // custom segment<br />
"crossId1": "Standard Episode ID", // episode ID<br />
"crossId2": "Content Originator ID", // content orginator (required for distributors)<br />
"airdate": "20161013 20:00:00", // airdate<br />
"adloadtype": "2", // ad load flag<br />
"hasAds": "1", // content contains ads = 1 / no ads = 0<br />
"progen": "CV" // program genre abbreviation<br />
},<br />
"ad": {<br />
"type": "preroll", // type of ad<br />
"assetid": "AD-ID123" // unique ID for ad<br />
}<br />
},<br />
<br />
// 3.3 Configure Payload: events<br />
"event": "playhead", //event name<br />
"position": "300", // position in seconds<br />
"type": "content", //"content" or "ad"<br />
"utc": "1456448742000" //unix timestamp in milliseconds <br />
}<br />
<br />
// Append payload to URL<br />
var image = new Image()<br />
image.onerror = function() {<br />
// wait and send again<br />
}<br />
(new Image).src = sessionURL+encodeURI(JSON.stringify(payload));<br />
</syntaxhighlight><br />
<br />
==== Enable Debug Logging ====<br />
<br />
Now that you have set up the Cloud API requests, you can enable debug logging to validate your integration. Enabling debug logging is required for Nielsen certification.<br />
<br />
==== GET Request ====<br />
<br />
Display GET Request to console using a name to identify each event (e.g. playhead).<br />
<br />
<syntaxhighlight lang="javascript"><br />
console.log("Event", image); <br />
</syntaxhighlight><br />
<br />
==== Payload ====<br />
<br />
Output payload to identify required metadata and events.<br />
<br />
<syntaxhighlight lang="javascript"><br />
console.log("Event Payload", payload); <br />
</syntaxhighlight><br />
<br />
==== HTTP Response Code ====<br />
<br />
Confirm request was completed by viewing HTTP response code.<br />
<br />
<syntaxhighlight lang="javascript"><br />
code = msg.GetResponseCode();<br />
console.log("Response Code", code); <br />
</syntaxhighlight><br />
<br />
You can reference the HTTP Response Code table when reviewing your requests:<br />
<br />
{| class="wikitable"<br />
|-<br />
! Status Code !! Status Text !! Description<br />
|-<br />
| <code>200</code> || OK || request received<br />
|-<br />
| <code>403</code> || Forbidden || invalid App ID<br />
|-<br />
| <code>404</code> || Not Found || JSON issue<br />
|}<br />
<br />
== Opt-Out ==<br />
Your app must provide a means for the user to Opt-Out, or Opt-In to Nielsen Measurement. This requirement can be fulfilled by checking the device OS for the user's setting of "Limit Ad Tracking" or similar option. If the device offers "Limit Ad Tracking" settings, you should set <code>uoo=true</code> or <code>uoo=false</code> depending on the user's privacy setting. Also, ensure that the <code>devId</code> is set to a blank value if the user elects to opt-out.<br />
<br />
If the device does not have OS-level "Do Not Track" settings, you can implement opt-out by creating an Opt-Out/Opt-In button, toggle switch, or slider within the app "Settings", or "About" section to allow the user selection.<br />
<br />
[[File:Nielsen Opt-Out.png|link=]]<br />
<br />
You will need to store the User Opt-Out (uoo) status, so that it can be retrieved and populated in the <code>"devInfo"</code> metadata which will be sent in every Cloud API event (playhead, complete, & delete). <br />
<br />
{| class="wikitable"<br />
|-<br />
! uoo Key !! Description !! Values<br />
|-<br />
| uoo || Device is Opted-In to Nielsen Measurement (Recommended Default Setting) || <code>"false"</code><br />
|-<br />
| uoo || Device is Opted-Out of Nielsen Measurement || <code>"true"</code><br />
|}<br />
<br />
===== devInfo Opt-In JSON Payload Example =====<br />
<br />
<syntaxhighlight lang="javascript"><br />
"devInfo": {<br />
"apn": "Cloud API Sample App",<br />
"apv": "1",<br />
"devId": "7be25cf9-8c40-5cc2-871e-19bf41940288",<br />
"uoo": "false"<br />
},<br />
</syntaxhighlight><br />
<br />
===== devInfo Opt-Out JSON Payload Example =====<br />
<br />
<syntaxhighlight lang="javascript"><br />
"devInfo": {<br />
"apn": "Cloud API Sample App",<br />
"apv": "1",<br />
"devId": "", //devId must be blank when a user elects to Opt-Out.<br />
"uoo": "true"<br />
},<br />
</syntaxhighlight><br />
<br />
===== Privacy Information Template To Include In Opt-Out Screen =====<br />
<br />
<br />
Additionally, all applications must display the Nielsen privacy policy within their application, typically in the Settings/About screen of your application. The Nielsen privacy policy text is listed below.<br />
<br />
<blockquote><br />
'''''ABOUT NIELSEN MEASUREMENT'''''<br />
<br />
Television and the way we watch it have come a long way since Nielsen began measuring TV audiences in 1950. Today, the ability to watch our favorite shows at any time and on multiple devices amplifies the need for exceptionally adept and flexible audience measurement capabilities.<br />
<br />
Consumers are changing with the times, and the same goes for us. As technology continues to evolve and media companies try new ways to attract viewers, understanding what consumers are watching — and what they're watching on — is more important than ever. Today, viewing video is a personal and mobile experience — anytime and anywhere. Our capabilities provide relevant metrics that are necessary to inform successful marketing and programming and drive continued growth. As a global information and measurement leader, we are committed to protecting the privacy and security of the data we collect, process and use. While our digital measurement products are not used to identify you in any way, they help us and our clients measure and analyze how consumers engage with media across online, mobile and emerging technologies, and offer insights into consumer behavior.<br />
<br />
'''''YOUR CHOICES'''''<br />
<br />
Nielsen believes that you should have a choice about whether to contribute to our research and insights. To opt out or opt in to Nielsen measurement, please toggle your "Limit Ad Tracking" (or similar setting) on your device, or make the appropriate selection within the application's settings. If you have this app on more than one device, you will need to opt out of this app on each device. To learn more about our digital measurement products and your choices in regard to them, please visit http://www.nielsen.com/digitalprivacy.<br />
<br />
'''''Disclosure Template'''''<br />
<br />
Additionally, you must update the App description information from the App Distribution Store with the Nielsen Measurement disclosure below:<br />
<br />
This app features Nielsen's proprietary measurement software which will allow you to contribute to market research, like Nielsen's TV Ratings. Please see http://www.nielsen.com/digitalprivacy for more information.<br />
</blockquote><br />
<br />
== Testing ==<br />
Before providing an app build to Nielsen for testing, it is important to run validation checks once you have enabled debug logging.<br />
<br />
=== Payload Validation ===<br />
<br />
Ensure that all of the required payload data is populating while testing several videos. The following areas are critical to measurement:<br />
*devInfo<br />
*Asset metadata for both content, and ads<br />
*Events<br />
*Opt-Out status<br />
<br />
=== Player Events ===<br />
Review event calls:<br />
<br />
==== playhead ====<br />
*Playhead position updates every 10 seconds starting at position '0' for each new asset.<br />
*Final playhead position is sent on content, or ad before switching between assets.<br />
*Content metadata remains constant throughout an episode, or clip play.<br />
*Ad metadata is populated appropriately for each individual ad.<br />
*Playhead position update resumes for content after an ad break, and resets to 0 for each individual ad.<br />
*For scrubbing, last current position should be sent while scrubbing occurs, and the new position should also be sent where the user scrubs to.<br />
*Exiting a stream early should execute the last current position in a playhead update to receive accurate duration.<br />
*Upon pause, the current position should be sent, and playhead updates should stop incrementing until resume play occurs.<br />
<br />
==== complete ====<br />
*Check that the complete event executes upon content complete after the final playhead update is sent<br />
*Do not execute the complete event for ads<br />
<br />
==== delete ====<br />
*Check to see that the delete event occurs upon app exit, if the platform has the necessary exit callback events.<br />
<br />
==== GET Request Format ====<br />
*Ensure that the event payloads are formatted in JSON<br />
*Check to see that each of the Cloud API GET requests are properly encoded<br />
<br />
==== HTTP Response ====<br />
*Make sure that each of the Cloud API Get requests are received by the Nielsen Cloud API properly through use of the HTTP Response Code outputs enabled in console.<br />
<br />
==== Opt-Out ====<br />
*Test the "uoo" key gets populated accurately for both Opt-In and Opt-Out selections by validating the Cloud API events called after the user Opt-Out/Opt-In selection.<br />
*Test that the devId field is populated with a blank value if a user has elected to Opt-Out. For example: "devId": "",<br />
*If the device supports "Limit Ad Tracking" or has device "Opt-Out" settings, test that uoo=true, and that devId is set to a blank value if enabled in the device settings.<br />
<br />
== Go Live ==<br />
After your integration has been certified, you will need to: Change Endpoint and Disable Logging.<br />
<br />
'''Change Endpoint:''' You will need to update to the production endpoint:<br />
<br />
*Testing: <code>https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/</code><br />
*Production: <code>https://cloudapi.imrworldwide.com/nmapi/v2/</code><br />
<br />
Your production URL structure should now be:<br />
<code>https://cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=[payload]</code><br />
<br />
'''Disable Logging:''' You can now disable debug logging</div>AlexGutierrezhttps://engineeringportal.nielsen.com//w/index.php?title=DCR_Video_Android_SDK&diff=3067DCR Video Android SDK2018-12-12T17:48:37Z<p>AlexGutierrez: </p>
<hr />
<div>{{Breadcrumb|}} {{Breadcrumb|Digital}} {{Breadcrumb|DCR & DTVR}} {{CurrentBreadcrumb}}<br />
[[Category:Digital]]<br />
<br />
== Overview ==<br />
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.<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), and [[Digital Audio]]. Nielsen SDKs are also equipped to measure static content and can track key life cycle events of an application like:<br />
*Application launch events and how long app was running<br />
*Time of viewing a sub section / page in the application.<br />
<br />
== Prerequisites ==<br />
To start using the App SDK, the following details are required:<br />
* '''App ID (appid):''' Unique ID assigned to the player/site and configured by product.<br />
* '''sfcode:''' Unique identifier for the environment that the SDK should point to.<br />
* '''Nielsen SDK:''' The Nielsen SDK package contains a variety of sample players for your reference.<br />
If you do not have any of these prerequisites or if you have any questions, please contact our SDK sales support team.<br />
Refer to [[Digital Measurement Onboarding]] guide for information on how to get a Nielsen App SDK and appid.<br />
<br />
== Implementation ==<br />
This guide covers implementation steps for Android Studio utilizing the Standard Nielsen SDK for DCR.<br />
<br />
== Setting up your Development Environment ==<br />
<br />
=== Configuring Android Development Environment ===<br />
*The Nielsen App SDK (located in the [https://engineeringportal.nielsen.com/docs/Special:Downloads Downloads section] of the website) class is the primary application interface to the Nielsen App SDK on Android.<br />
*The Nielsen App SDK class is defined as the only public class belonging to the com.nielsen.app.sdk package.<br />
<br />
'''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.'''<br />
<br />
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.<br />
* '''For Video player applications'''<br />
** The Android OS hosting the App SDK should use a media player supporting HLS streaming (Android 3.0 and later will support it natively).<br />
** 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.<br />
* '''For Audio player applications'''<br />
** 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.<br />
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'''.<br />
Ensure the AppSdk.jar file is added in 'build.grade (App Level) file.<br />
* App SDK 1.2 provides support for x86, mips, and armeabi-7a architecture.<br />
<br />
==== Google Play Services ====<br />
Add the Google Play Services in the project,<br />
Steps: Android Studio -> File -> Project Structure ->(In module selection) select App -> Dependencies (tab) -> Click "+" button and select <code>"com.google.android.gms:play-services"</code>.<br />
Ensure it is added in build.gradle (App level) file<br />
<br />
==== Manifest File ==== <br />
* Add the following permissions on the project’s ''AndroidManifest.xml'' file.<br />
<syntaxhighlight lang="java"><uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" android:required="false" /><br />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/><br />
<uses-permission android:name="android.permission.INTERNET"/></syntaxhighlight><br />
For more details to handle runtime permissions in Android versions, please visit [https://developer.android.com/training/permissions/requesting.html]. <br />
<br />
* In <code>AndroidManifest.xml </code>under <application> node add the following metadata<br />
<br />
<syntaxhighlight lang="java"><meta-data <br />
android:name="com.google.android.gms.version" <br />
android:value="@integer/google_play_services_version"/></syntaxhighlight><br />
<br />
* App SDK checks to see if there is a Google service available and updated.<br />
* 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.<br />
<br />
==== Library ====<br />
Nielsen App SDK uses the following packages/classes from the Google Play service.<br />
* google-play-services_lib<br />
<br />
==== Classes/package ====<br />
* com.google.android.gms.ads.identifier.AdvertisingIdClient;<br />
* com.google.android.gms.ads.identifier.AdvertisingIdClient.Info;<br />
* com.google.android.gms.common.ConnectionResult;<br />
* com.google.android.gms.common.GooglePlayServicesUtil;<br />
* com.google.android.gms.common.GooglePlayServicesRepairableException;<br />
* com.google.android.gms.common.GooglePlayServicesNotAvailableException;<br />
<br />
== SDK Initialization ==<br />
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)<br />
<br />
* A maximum of four SDK instances per appid are supported. <br />
* When four SDK instances exist, you must destroy an old instance before creating a new one.<br />
<br />
The following table contains the list of arguments that can be passed via the AppInfo JSON schema.<br />
<br />
* The appid is provided by the Nielsen Technical Account Manager (TAM). The appid is a GUID data type and is specific to the application.<br />
{| class="wikitable"<br />
|-<br />
! Parameter / Argument !! Description !! Source !! Required? !! Example<br />
|-<br />
| appid || Unique id for the application assigned by Nielsen. It is GUID data type.|| Nielsen-specified || Yes || PXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX<br />
|-<br />
| appname || Name of the application || Client-defined || Optional; automatically detected in SDK 6.0.0.4 and above || Nielsen Sample App<br />
|-<br />
| sfcode || Nielsen collection facility to which the SDK should connect.<br />
'''DTVR'''<br />
* "us"<br />
'''Digital Audio'''<br />
* "drm"<br />
'''DCR'''<br />
* "dcr" <br />
|| Nielsen-specified || Yes || dcr-cert<br />
|-<br />
|containerID || View ID of the UI element used as player view in application for Viewability ||Client-defined||Optional||"1234567"<br />
|-<br />
| nol_devDebug || Enables Nielsen console logging. Only required for testing<br />
|| Nielsen-specified || Optional || "DEBUG"<br />
|}<br />
<br />
== Debug flag for development environment ==<br />
Player application developers / integrators can use Debug flag to check whether an App SDK API call made is successful. To activate the Debug flag,<br />
Pass the argument <code>@"nol_devDebug":@"INFO"</code>, in the JSON string . The permitted values are:<br />
<br />
* '''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.<br />
* '''WARN''': Indicates potential integration / configuration errors or SDK issues.<br />
* '''ERROR''': Indicates important integration errors or non-recoverable SDK issues.<br />
* '''DEBUG''': Debug logs, used by the developers to debug more complex issues.<br />
<br />
Once the flag is active, it logs each API call made and the data passed. The log created by this flag is minimal.<br />
<blockquote>'''Note''': DO NOT activate the Debug flag in a production environment.</blockquote><br />
<br />
==== Sample SDK Initialization Code ====<br />
[[AppSDK()]] is no longer a singleton object and should be initialized as below.<br />
<br />
'''Initialization of App SDK object through a JSON object'''<br />
<syntaxhighlight lang="java"> <br />
<br />
try<br />
{<br />
// Prepare AppSdk configuration object (JSONObject)<br />
JSONObject appSdkConfig = new JSONObject()<br />
.put("appid", "PDA7D5EE6-B1B8-XXXX-XXXX-2A788BCXXXCA")<br />
.put("sfcode", "dcr")<br />
.put("containerID": "2131558561")<br />
.put("nol_devDebug", "DEBUG"); // only for debug builds<br />
<br />
// Pass appSdkConfig to the AppSdk constructor<br />
mAppSdk = new AppSdk(appContext, appSdkConfig, appSdkListener);<br />
}<br />
catch (JSONException e)<br />
{<br />
Log.e(TAG, "Couldn’t prepare JSONObject for appSdkConfig", e);<br />
}<br />
</syntaxhighlight><br />
Here, <code>appContext</code> is the App context object and <code>appSdkConfig</code> is JSON object for holding the parameters (<code>appid</code>, <code>sfcode</code>) the App passes to the Nielsen App SDK via a JSON string. The appid is obtained from Nielsen operational support and is unique to the app.<br />
<br />
<br />
The integration of Nielsen App SDK will depend on type of client app.<br /><br />
* Ensure that SDK files (AppSdk.jar and libAppSdk.so [App SDK 1.2 Only]) are included under the App’s project and the App SDK is linked to the App (the setting to link App SDK to the App can be found on property page of the App’s project).<br />
<br />
<!-- == Initializing the Nielsen AppSDK to measure the Viewability ==<br />
The integrator to support the viewability metrics in the application has to provide a tag value of the player view to let Nielsen AppSDK know that there is a player that needs to be tracked. It’s called the ‘containerId’ and it should be passed in application info dictionary as string while initializing the Nielsen AppSDK.<br />
<br />
==== Android ====<br />
{| class="wikitable"<br />
|-<br />
! # !! Parameter Name !! Description !! Supported Values !! Example<br />
|-<br />
| 1 || containerId ||View ID of the UI element used as player view in application. getId() method of View class can be used to get this value. ||A positive integer used to identify the view. || 2131558561<br />
|}<br />
<br />
==== iOS ====<br />
{| class="wikitable"<br />
|-<br />
! # !! Parameter Name !! Description !! Supported Values !! Example<br />
|-<br />
| 1 || containerId ||The tag of the UIView that represents the Player View ||The string value representing the NSInteger value with maximum value of NSIntegerMax that is related on 32- or 64-bit applications. || "100" <br> "2131558561"<br />
|}<br />
<br />
For iOS it is required to link additional frameworks that are needed for viewability engine:<br><br />
<code>JavaScriptCore.framework</code> <br><br />
<code>WebKit.framework</code><br />
<br />
The Nielsen AppSDK uses a tracking WebView (TWV) approach. For more information on Viewability, please refer to [https://engineeringportal.nielsen.com/docs/Implementing_Viewability_with_AppSDK Implementing Viewability with AppSDK.]<br />
--><br />
<br />
== APP SDK Error & Event Codes ==<br />
To view the Error and Event codes for iOS and Android, please review the [[APP SDK Event Codes|App SDK Event Code]] Reference page.<br />
<br />
== Configure Payload ==<br />
=== Handling JSON Metadata ===<br />
All the SDK methods handles only two types of objects: NSString, NSDictionary. The parameters passed must be either a JSON formatted string or a NSDictionary object. The JSON passed in the SDK must be well-formed.<br />
* NSDictionary object<br />
** If an object of unexpected type is passed to the method, the error message will be logged.<br />
** If string has invalid JSON format, the error message will be logged.<br />
* JSON value must be string value.<br />
** This includes boolean and numeric values. For example, a value of true should be represented with "true", number value 123 should be "123".<br />
** All the Variable Names like appid, appname, sfcode, dataSrc, title, type etc. are case-sensitive. Use the correct variable name as specified in the documentation.<br />
* JSON string can be prepared using either raw NSString or serialized NSDictionary.<br />
<syntaxhighlight lang="java"><br />
JSONObject channelInfo = new JSONObject()<br />
.put("channelname","My Channel Name 1")<br />
<br />
JSONObject contentMetadata = new JSONObject()<br />
//SDK Metadata<br />
.put("type", "content")<br />
.put("assetid", "vid345-67483")<br />
.put("program", "Program Name")<br />
.put("title", "Program S3, EP1")<br />
.put("length", "3600")<br />
.put("segB", "CustomSegmentValueB") //optional<br />
.put("segC", "CustomSegmentValueC") //optional<br />
.put("isfullepisode", "yes")<br />
.put("airdate", "20161013 20:00:00")<br />
</syntaxhighlight><br />
<br />
=== Configure metadata === <br />
channelName should remain constant throughout the completion of an episode or live stream.<br />
{| class="wikitable"<br />
|-<br />
! Key !! Description !! Values !! Required<br />
|-<br />
| channelName || ChannelInfo refers to the Channel name. This can be a free-form value<br />
value such as a friendly name for the content being played. If no name<br/><br />
is available, pass the urlstring of the content being played.<br />
|| custom || ✓<br />
|-<br />
|}<br />
<br />
=== Content metadata ===<br />
Content metadata should remain constant throughout the entirety of an episode/clip including when ads play.<br />
{{DCR Content Metadata}}<br />
<br />
=== Ad Metadata ===<br />
The ad metadata (if applicable) should be passed for each individual ad, if ads are available during or before the stream begins.<br />
<br/><br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| type || type of ad || 'preroll', 'midroll', or 'postroll' || ✓<br />
|-<br />
| assetid || unique ID assigned to ad || custom || ✓<br />
|}<br />
== Sequence of Calls ==<br />
=== play ===<br />
Use [[DCR_Video_APP_SDK#play|play]] to pass the channel descriptor information through channelName parameter when the user taps the '''Play''' button on the player.<br />
<syntaxhighlight lang="java"> public void play(JSONObject channelInfo);</syntaxhighlight><br />
<br />
=== loadMetadata ===<br />
<syntaxhighlight lang="java">public void loadMetadata(JSONObject contentMetadata);</syntaxhighlight><br />
<br />
=== playheadPosition ===<br />
<syntaxhighlight lang="java"><br />
public void setPlayheadPosition(long position)<br />
</syntaxhighlight><br />
<br />
=== stop ===<br />
<syntaxhighlight lang="java">public void stop()</syntaxhighlight><br />
<br />
=== end ===<br />
When content stop is initiated and content cannot be resumed from the same position (it can only be restarted from the beginning of stream).<br />
<syntaxhighlight lang="java">public void end()</syntaxhighlight><br />
<br />
=== Example Ad Object ===<br />
<syntaxhighlight lang="javascript"><br />
// create ad object<br />
"ad": {<br />
"type": "preroll",<br />
"assetid": "AD-ID123"<br />
}<br />
</syntaxhighlight><br />
<br />
== Configure API Calls ==<br />
<br />
=== Sample API Sequence ===<br />
A Sample API sequence could follow this flow:<br />
{| class="wikitable"<br />
|-<br />
! Type !! Sample code !! Description<br />
|-<br />
|On App Start||<code>[nielsenMeter loadMetadata: contentMetadata];</code> || // contentMetadata Object contains the JSON metadata for the impression<br />
|-<br />
| rowspan="2" | Start of stream || <code>[nielsenMeter play: channelName];</code> || // channelName contains JSON metadata of channel/video name being played<br />
|-<br />
| <code>[nielsenMeter loadMetadata: contentMetadataObject];</code> || // contentMetadataObject contains the JSON metadata for the content being played<br />
|-<br />
| Content || <code>[nielsenMeter setplayheadPosition: position];</code> || // playheadPosition is position of the playhead while the content is being played<br />
|-<br />
| End of Stream || <code>[nielsenMeter end];</code> || // Content playback is completed.<br />
|}<br />
<br />
=== SDK Events ===<br />
{| class="wikitable"<br />
|-<br />
! Event !! Parameter !! Description<br />
|-<br />
| 'loadMetadata' || content/ad metadata object || Needs to be called at the beginning of each asset<br />
|-<br />
| 'setPlayheadPosition' || playhead position as integer<br/><br />
VOD: || current position in seconds <br/><br />
Live: current Unix timestamp (seconds since Jan-1-1970 UTC) <br/><br />
Note: 'setPlayheadPosition' has to be called every second<br />
||<br />
Pass playhead position every second during playback<br />
|-<br />
| 'stop' || playhead position || Call when content or ads complete playing and pass playhead position<br />
|-<br />
| 'end' || playhead position in seconds || Call when the current video asset completes playback and pass the playhead position. <br/><br />
Example: At the end of the content stream, if the user switches to another piece of content, when the browser is refreshed or closed.<br />
|}<br />
<blockquote>Note: For livestream, send the Unix timestamp; for VOD send the time in seconds as integer. The final playhead position must be sent for the current asset being played before calling <code>'''stop'''</code>, <code>'''end'''</code> or<code> '''loadmetadata'''</code>,.</blockquote><br />
<br />
=== Life cycle of SDK instance ===<br />
Life cycle of SDK instance includes four general states:<br />
# '''Initial state''' – The SDK is not initialized and hence, not ready to process playing information. Once the SDK is moved out of this state, it needs instantiation of the new SDK instance in order to get the instance in the '''Initial state'''.<br />
# '''Idle state''' – The SDK is initialized and is ready to process playing information. Once Initialized, the SDK instance is not processing any data, but is listening for the play event to occur.<br />
# '''Processing state''' – The SDK instance is processing playing information. The <code>'''play'''</code> and <code>'''loadMetadata''' </code> calls move the SDK instance into this state. In this state, the SDK instance will be able to process the following calls.<br />
## <code>'''setplayheadPosition'''</code> – Call this API every one second when playhead position timer is fired. If a LIVE event, use the current Unix timestamp (seconds since Jan-1-1970 UTC).<br />
## <code>'''stop'''</code> – Call this API when the playback is paused, switches between content and ad (within the same content playback) or encounters interruptions.<br />
## <code>'''end'''</code> – SDK instance exits from Processing state when this API is called.<br />
# '''Disabled state''' – The SDK instance is disabled and is not processing playing information. SDK instance moves into this state in one of the following scenarios.<br />
## Initialization fails<br />
## <code>'''appDisableApi'''</code> is set to <code>true</code><br />
<br />
<blockquote>'''Note:''' For API Version 5.1 and above, App SDK will fire data pings and continue measurement even after the user has opted out from Nielsen measurement on a device. The data ping will be marked as opted-out ping.<br />
<br />
'''Note''': In case of any interruptions during playback due to alarm, calendar, call, flight mode, Wi-Fi toggle, channel change, etc., call [[stop]] to stop the measurement.<br />
* As soon as the playback resumes, call <code>'''play'''</code>, <code>'''loadMetadata''' </code> and <code>'''playheadPosition'''</code> </blockquote><br />
<br />
=== API Call Sequence ===<br />
==== Use Case 1: Content has no Advertisements ====<br />
Call [[play()]] with channelName JSON as below.<br />
<syntaxhighlight lang="json">{<br />
"channelName": "TheMovieTitle"<br />
}</syntaxhighlight><br />
Call [[loadMetadata()]] with JSON metadata for content as below.<br />
<syntaxhighlight lang="json">{<br />
"type": "content",<br />
"assetid": "vid345-67483",<br />
"program": "ProgramName",<br />
"title": "Program S3, EP1",<br />
"length": "3600",<br />
...<br />
}</syntaxhighlight><br />
Call [[setPlayheadPosition()]] every one second until a pause / stop.<br />
Use the sample API sequence below as a reference to identify the specific events that need to be called during content playback without ads.<br />
{| class="wikitable"<br />
|-<br />
! Type !! Sample code !! Description<br />
|-<br />
| rowspan="2" | Start of stream || <code>mAppSdk.play(channelName); </code> || // channelName contains JSON metadata of channel/video name being played<br />
|-<br />
| <code>mAppSdk.loadMetadata(contentMetaDataObject);</code> || // contentMetadataObject contains the JSON metadata for the content being played<br />
|-<br />
| Content || <code>mAppSdk.setPlayheadPosition(playheadPosition);</code> || // position is position of the playhead while the content is being played<br />
|-<br />
| End of Stream || <code>mAppSdk.end();</code> || // Content playback is completed.<br />
|}<br />
<br />
==== Use Case 2: Content has Advertisements ====<br />
Call [[play()]] with channelName JSON as below.<br />
<syntaxhighlight lang="json">{<br />
"channelName": "TheMovieTitle"<br />
}</syntaxhighlight><br />
Call [[loadMetadata()]] with JSON metadata for ad as below.<br />
<syntaxhighlight lang="json">{<br />
"type": "preroll",<br />
"assetid": "ad=123"<br />
}</syntaxhighlight><br />
<blockquote>Note: In case the individual ad details are not available, send ad pod (presence) details through the [[loadMetadata]] and playhead position through [[playheadPosition]].</blockquote><br />
<br />
Call [[setPlayheadPosition()]] every one second until a pause / stop / another [[loadMetadata()]] is called. Playhead should be passed for the entire duration of ad pod, if the ad pod details are passed as part of [[loadMetadata()]].<br />
<br />
'''Ad Content'''<br />
<syntaxhighlight lang="java"> long pos = mAdPlayer.videoPosition() / 1000;<br />
if (mAppSdk != null)<br />
{<br />
mAppSdk.setPlayheadPosition(pos);<br />
}</syntaxhighlight><br />
<blockquote>Note: The playhead positions for ad and content should be maintained separately.</blockquote><br />
<br />
The sample API sequence can be used as a reference to identify the specific events that need to be called during content and ad playback.<br />
{| class="wikitable"<br />
|-<br />
! Type !! Sample code !! Description<br />
|-<br />
| rowspan="2" | Start of stream || <code>mAppSdk.play(channelName); </code> || // channelName contains JSON metadata of channel/video name being played<br />
|-<br />
| <code>mAppSdk.loadMetadata(contentMetaDataObject);</code> || // contentMetadataObject contains the JSON metadata for the content being played<br />
|-<br />
| rowspan="3" | Preroll || <code>mAppSdk.loadMetadata(prerollMetadataObject);</code> || // prerollMetadataObject contains the JSON metadata for the preroll ad<br />
|-<br />
| <code>mAppSdk.setPlayheadPosition(playheadPosition);</code> || // position is position of the playhead while the preroll ad is being played<br />
|-<br />
| <code>mAppSdk.stop();</code> || // Call stop after preroll occurs<br />
|-<br />
| rowspan="3" | Content || <code>mAppSdk.loadMetadata(contentMetaDataObject);</code> || // contentMetadataObject contains the JSON metadata for the content being played<br />
|-<br />
| <code>mAppSdk.setPlayheadPosition(playheadPosition);</code> || // position is position of the playhead while the content is being played<br />
|-<br />
| <code>mAppSdk.stop();</code> || // Call stop after the content is paused (ad starts)<br />
|-<br />
| rowspan="6" | Midroll || <code>mAppSdk.loadMetadata(midrollMetaDataObject);</code> || // midrollMetadataObject contains the JSON metadata for the midroll ad<br />
|-<br />
| <code>mAppSdk.setPlayheadPosition(playheadPosition);</code> || // position is position of the playhead while the midroll ad is being played<br />
|-<br />
| <code>mAppSdk.stop();</code> || // App moves to background(midroll pauses) <br />
|-<br />
| <code>mAppSdk.loadMetadata(midrollMetaDataObject);</code> || // App moves to foreground (midroll resumes) <br />
|-<br />
| <code>mAppSdk.setPlayheadPosition(playheadPosition);</code> || // playheadPosition is position of the playhead while the midroll ad is being played <br />
|-<br />
| <code>mAppSdk.stop();</code> || // Call stop after midroll occurs<br />
|-<br />
| rowspan="3" | Content (End of stream) || <code>mAppSdk.loadMetadata(contentMetaDataObject);</code> || // contentMetadataObject contains the JSON metadata for the content being played<br />
|-<br />
| <code>mAppSdk.setPlayheadPosition(playheadPosition);</code> || // position is position of the playhead while the content is being played<br />
|-<br />
| <code>mAppSdk.stop();</code> || // Always call stop irrespective of postroll is followed or not<br />
|-<br />
| End of Stream || <code>mAppSdk.end();</code> || // Call end() at the end of content<br />
|-<br />
| rowspan="3" | Postroll || <code>mAppSdk.loadMetadata(postrollMetaDataObject);</code> || // postrollMetadataObject contains the JSON metadata for the postroll ad<br />
|-<br />
| <code>mAppSdk.setPlayheadPosition(playheadPosition);</code> || // position is position of the playhead while the postroll ad is being played<br />
|-<br />
| <code>mAppSdk.stop();</code> || // Call stop after postroll occurs<br />
|}<br />
<br />
<blockquote>Note: Each Ad playhead should reset or begin from 0 at ad start. When content has resumed following an ad break, playhead position must continue from where previous content segment was left off.</blockquote><br />
<br />
== Handling Foreground and Background states ==<br />
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<br />
* Enable the Nielsen SDK to measure background/foreground state by makingthe relevant update to the AndroidManifest.<br />
* Integrate Nielsen’s SdkBgFgDetectionUtility class within your Custom Application Class.<br />
* Custom implementation of the required methods within your application.<br />
<br />
=== ForeGround/Background Measurement via AndroidManifest ===<br />
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.<br />
<syntaxhighlight lang="java"><br />
<application android:name="com.nielsen.app.sdk.AppSdkApplication"><br />
</syntaxhighlight><br />
<br />
=== Using the Android SdkBgFbDetectionUtility Class ===<br />
For developers who are already using the application class, it is recommended that background/foreground state is implemented using the [https://engineeringportal.nielsen.com/docs/Android_Background_Foreground SdkBgFgDetectionUtility class]. The [https://engineeringportal.nielsen.com/docs/Android_Background_Foreground SdkBgFgDetectionUtility class] is compatible with Android 4+ and has been made available to Nielsen clients. (You will need to copy/paste the code provided into a file).<br />
<br />
=== Manual Background/ForeGround State Management ===<br />
In cases where the developer is not able to use the AndroidManifest.xml solution nor the Nielsen provided [https://engineeringportal.nielsen.com/docs/Android_Background_Foreground 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.<br />
<br />
The SDK is informed about app state using the below methods.<br />
<syntaxhighlight lang="java"><br />
AppLaunchMeasurementManager.appInForeground(getApplicationContext());<br />
AppLaunchMeasurementManager.appInBackground(getApplicationContext());<br />
</syntaxhighlight><br />
Within the lifecycle of individual activities, onResume() and onPause() are best suited to providing indication of the app state.<br />
<br />
<br />
Correct measurement of the foreground/background state is crucial to Static App measurement within Nielsen Digital Content Ratings (DCR).<br />
<br />
== Interruptions during playback ==<br />
As part of integrating Nielsen App SDK with the player application, the Audio / Video app developer needs to handle the following possible interruption scenarios:<br />
* Pause / Play<br />
* Network Loss (Wi-Fi / Airplane / Cellular)<br />
* Call Interrupt (SIM or Third party Skype / Hangout call)<br />
* Alarm Interrupt<br />
* Content Buffering<br />
* Device Lock / Unlock (Video players only, not for Audio players)<br />
* App going in the Background/Foreground (Video players only, not for Audio players)<br />
* Channel / Station Change Scenario<br />
* Unplugging of headphone<br />
In case of encountering one of the above interruptions, the player application needs to<br />
* Call [[stop]] immediately (except when content is buffering) and withhold sending playhead position.<br />
* Start sending pings – <code>'loadMetadata'</code> and <code>'playheadPosition'</code> for the new viewing session, once the playback resumes.<br />
Please see the [https://engineeringportal.nielsen.com/docs/Digital_Measurement_Interruption_Scenarios Interruption Scenarios Page] for more details<br />
<br />
== Pre-Certification Checklists ==<br />
After the application is ready to be sent for Nielsen Certification, please go through the [[Digital Pre-Certification Checklist App SDK]] and ensure the app behaves as expected, before submitting to Nielsen.<br />
<br />
{{Template:Android_Privacy_and_Opt-Out}}<br />
<br />
== Going Live ==<br />
Following Nielsen testing, users need to make one update to the initialization call to ensure that the site is being measured properly.<br />
<br />
# '''Debug Logging''': Disable logging by deleting <code>{nol_sdkDebug: 'DEBUG'}</code> from initialization call.<br />
'''Note''': before going live you have to inform Nielsen team - this is necessary, because Nielsen team has to adjust internal configuration parameter to enable data collection. Without that notification no data will be collected and no data will be reported.<br />
<br />
== Sample Applications ==<br />
The below sample applications have been designed to show the API's functionality and are broken into two distinct categories:<br />
* '''Basic''' - To show the functionality of the Nielsen API using a standard no-frills player.<br />
** [[Swift Basic Sample|Swift 4.0 Sample]]<br />
** [[Objective-c Basic example|Objective-C Sample]]<br />
** [[Android Basic example|Android Studio Example]]<br />
<br />
* '''Advanced''' - Nielsen API integrated into a custom video player is bundled with the SDK.</div>AlexGutierrezhttps://engineeringportal.nielsen.com//w/index.php?title=Digital_Measurement_iOS_Simplified_API&diff=3002Digital Measurement iOS Simplified API2018-10-29T16:12:35Z<p>AlexGutierrez: </p>
<hr />
<div>{{Breadcrumb|}} {{Breadcrumb|Digital}} {{Breadcrumb|DCR & DTVR}} {{CurrentBreadcrumb}}<br />
[[Category:Digital]]<br />
<br />
== Overview ==<br />
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.<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:<br />
*Application launch events and how long app was running<br />
*Time of viewing a sub section / page in the application.<br />
<br />
__TOC__<br />
<br />
== Prerequisites ==<br />
To start using the App SDK, the following details are required:<br />
* '''App ID (appid):''' Unique ID assigned to the player/site and configured by product.<br />
* '''sfcode:''' Unique identifier for the environment that the SDK should point to.<br />
* '''Nielsen SDK:''' The Nielsen SDK package contains a variety of sample players for your reference.<br />
If you do not have any of these prerequisites or if you have any questions, please contact our SDK sales support team.<br />
Refer to [[Digital Measurement Onboarding]] guide for information on how to get a Nielsen App SDK and appid.<br />
<br />
== Simplified SDK API ==<br />
As part of making the SDK more user friendly and reduce the number of app integration touch points, Nielsen has designed a simple interface to pass metadata to the sdk while reducing the number of API calls. The new <code> trackevent() </code> API has been implemented as a wrapper for the existing SDK and will be responsible for handling new API calls, performing validation, and translation of 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.<br />
[[File:SimplifiedAPI_vs_StandardAPI_New.jpg|2048px|link=http://dayrhesdkp001z.enterprisenet.org/w/images/9/91/SimplifiedAPI_vs_StandardAPI_New.jpg]]<br />
Existing API has a number of methods used for reporting player and application state changes to the SDK. Order of calls is important for the SDK in the existing API. In the new enhanced 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 existing API in separate calls will be provided in one single call. SDK will analyse the data received in the dictionary object, compare it with the data received previously and generate a sequence of calls for the existing API.<br />
<br />
[[File:Co-Existance.jpg|center|700px]]<br />
''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).''<br />
<br />
''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.''<br />
<br />
== Implementation ==<br />
This guide covers implementation steps for iOS using Xcode and Android using Android Studio.<br />
<br />
== Setting up your Development Environment ==<br />
<big><br />
'''Configuring Xcode Development Environment''' <br />
</big> <br />
<br />
Nielsen App SDK is compatible with Apple iOS versions 8.0 and above.<br />
<br />
The SDK uses the NSURLSession instead of the deprecated NSURLConnection.<br />
<br />
<blockquote>'''Note''': All communications between the SDK and the Census (Collection Facility) use HTTPS.</blockquote><br />
<br />
<big>'''Importing Frameworks'''</big><br />
The first step is to ensure that the following frameworks and libraries are imported into the Frameworks folder of the Xcode project before creating an instance of the Nielsen App SDK object.<br />
* UIKit.framework<br />
* Foundation.framework<br />
* AdSupport.framework<br />
* JavascriptCore.framework<br />
* WebKit.framework<br />
* SystemConfiguration.framework<br />
* Security.framework<br />
** Nielsen Analytics framework makes use of a number of functions in this library.<br />
* AVFoundation.framework<br />
* NielsenAppApi.framework<br />
* libc++.tbd (as SDK contains Objective C++ source file)<br />
** Alternatively, include -lstdc++ in Build Settings → Other Linker Flag of the Xcode project<br />
<br />
<blockquote>'''Example'''<br />
* Extract “NielsenAppApi.Framework” from the Nielsen App SDK sample app and copy it to Frameworks folder of the Xcode project.<br />
* Add the code <code>-#import NielsenAppApi/NielsenAppApi.h</code> to the View Controller’s header file.</blockquote><br />
<br />
Ensure that the following are included in the Linked Frameworks and Libraries list (located in the project’s Summary settings).<br />
* Nielsen App SDK<br />
* iOS security framework<br />
<br /><br />
=== Using Swift ===<br />
To import a set of Objective-C files in the same app target as your Swift code, you rely on an Objective-C bridging header to expose those files to Swift. Xcode offers to create this header file when you add a Swift file to an existing Objective-C app, or an Objective-C file to an existing Swift app.<br />
*Select File/New File/Objective-C File<br />
*Xcode will prompt you to create a bridging header.<br />
[[File:bridgingheader 2x.png|600px|center|link=]]<br />
Once this file has been created, you need to add the following:<br />
<syntaxhighlight lang="swift"><br />
#import <NielsenAppApi/NielsenAppApi.h><br />
</syntaxhighlight><br />
=== Using Objective-C ===<br />
Add the code <br />
<syntaxhighlight lang ="objective-c"><br />
#import <NielsenAppApi/NielsenAppApi.h><br />
</syntaxhighlight><br />
to the View Controller’s header file.<br />
<br />
== SDK Initialization ==<br />
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)<br />
<br />
* 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:]]<br />
<br />
The following table contains the list of arguments that should be passed during initialization.<br />
<br />
* The appid is provided by the Nielsen Technical Account Manager (TAM). <br />
{| class="wikitable"<br />
|-<br />
! Parameter / Argument !! Description !! Source !! Required? !! Example<br />
|-<br />
| appid || Unique id for the application assigned by Nielsen.<br />
It is GUID data type, provided by the Nielsen Technical Account Manager (TAM). <br />
|| Nielsen-specified || Yes || PXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX<br />
|-<br />
| appname || Name of the application || Client-defined || No|| "Nielsen Sample App"<br />
|-<br />
| appversion || Current version of the app used || Client-defined || No || "1.0.2"<br />
|-<br />
| sfcode || Nielsen collection facility to which the SDK should connect.<br />
'''DTVR'''<br />
* "us"<br />
'''Digital Audio'''<br />
* "drm"<br />
'''DCR'''<br />
* "dcr" <br />
|| Nielsen-specified || Yes || "dcr"<br />
|-<br />
|containerID || View ID of the UI element used as player view in application for Viewability (Future Feature) ||Client-defined||No||"1234567"<br />
|-<br />
|nol_devDebug || Enables Nielsen console logging. Only required for testing ||Nielsen-specified || Optional || "DEBUG"<br />
|}<br />
<br /><br />
<br />
<br />
==== Sample SDK Initialization Code ====<br />
{{ExampleCode|<br />
|Swift = <br />
Swift 4.0 Example:<br />
<code>NielsenInit.swift</code><br />
<syntaxhighlight lang="swift"><br />
<br />
import Foundation<br />
import NielsenAppApi<br />
<br />
class NielsenInit : NSObject {<br />
class func createEventTracker(delegate: NielsenEventTrackerDelegate) -> NielsenEventTracker?{<br />
<br />
//Initialising the NielsenEventTracker class by passing app information which returns the instance of NielsenEventTracker.<br />
<br />
var nielsenEventTracker: NielsenEventTracker?<br />
<br />
let appInformation = [<br />
<br />
"appid": "PDA7D5EE6-B1B8-4123-9277-2A788XXXXXXX",<br />
"appversion": "1.0",<br />
"appname": "Amazing app",<br />
"sfcode": "dcr",<br />
"nol_devDebug": "DEBUG",<br />
"containerId": String(containerId) //Keep container id unique constant, you can use tag property of player.<br />
]<br />
<br />
nielsenEventTracker = NielsenEventTracker(appInfo:appInformation1, delegate:delegate)<br />
return nielsenEventTracker<br />
}<br />
}<br />
</syntaxhighlight><br />
<br />
<br />
Sample code: ViewController<br />
<br /><br />
<code>ViewController.swift</code><br />
<br />
<syntaxhighlight lang="swift"><br />
<br />
override func viewDidLoad() {<br />
super.viewDidLoad()<br />
<br />
//Getting the instance of NielsenEventTracker<br />
<br />
self.nielsenEventTracker = NielsenInit.createEventTracker(delegate: self)<br />
</syntaxhighlight><br />
<br />
|Objective C = <br />
Initialize the Nielsen App object within the viewDidLoad view controller delegate method using initWithAppInfo:delegate:<br />
<blockquote>If App SDK is initialized using init or new methods, it will ignore the API calls resulting in no measurement. The SDK will not return any errors.</blockquote><br />
Objective-C Example:<br />
<code>NielsenInit.m</code><br />
<syntaxhighlight lang="objective-c"> <br />
#import "NielsenInit.h"<br />
#import <NielsenAppApi/NielsenEventTracker.h><br />
<br />
@implementation NielsenInit<br />
<br />
+ (NielsenEventTracker *)createNielsenEventTrackerWithDelegate:(id<NielsenEventTrackerDelegate>)delegate<br />
{<br />
//Initialising the NielsenEventTracker class by passing app information which returns the instance of NielsenEventTracker.<br />
<br />
NSDictionary *appInformation = @{ @"appid": @"PDA7D5EE6-B1B8-4123-9277-2A788XXXXXXX",<br />
@"appversion": @"1.0",<br />
@"appname": @"Objc Test app",<br />
@"sfcode": @"dcr",<br />
@"nol_devDebug": @"INFO",<br />
@"containerId": @"1" }; //Keep container id unique constant, you can use tag property of player.<br />
<br />
return [[NielsenEventTracker alloc] initWithAppInfo:appInformation delegate:delegate];<br />
}<br />
</syntaxhighlight><br />
<br />
<br />
The following would be the <code>NielsenInit.h</code> file:<br />
<syntaxhighlight lang="objective-c"><br />
#import <Foundation/Foundation.h><br />
<br />
@class NielsenEventTracker;<br />
@protocol NielsenEventTrackerDelegate;<br />
<br />
@interface NielsenInit : NSObject<br />
<br />
+ (NielsenEventTracker *)createNielsenEventTrackerWithDelegate:(id<NielsenEventTrackerDelegate>)delegate;<br />
<br />
@end<br />
</syntaxhighlight><br />
<br />
The ViewController.m file could then contain the following line(s):<br />
<syntaxhighlight lang="objective-c"> <br />
//Getting the instance of NielsenEventTracker<br />
nielsenEventTracker = [NielsenInit createNielsenEventTrackerWithDelegate:nil];<br />
/////<br />
-(void) setPlayHeadPosition {<br />
<br />
//Setting play head position<br />
CMTime timeInterval = CMTimeMakeWithSeconds(1, 1);<br />
[player addPeriodicTimeObserverForInterval:(timeInterval) queue:dispatch_get_main_queue() usingBlock:^(CMTime time){<br />
NSTimeInterval seconds = CMTimeGetSeconds(time);<br />
NSInteger intSec = seconds;<br />
NSString* strSec = [NSString stringWithFormat:@"%li", intSec];<br />
<br />
//updating playHead position in dictionary.<br />
[mutableData setValue:strSec forKey:@"playheadPosition"];<br />
<br />
//Sending data dictionary to SDK with updated playHead position.<br />
[nielsenEventTracker trackEvent:mutableData];<br />
}];<br />
}<br />
</syntaxhighlight><br />
<br />
}}<br />
<br />
<!--<br />
== Initializing the Nielsen AppSDK to measure the Viewability ==<br />
The integrator to support the viewability metrics in the application has to provide a tag value of the player view to let Nielsen AppSDK know that there is a player that needs to be tracked. It’s called the ‘containerId’ and it should be passed in application info dictionary as string while initializing the Nielsen AppSDK.<br />
<br />
==== Android ====<br />
{| class="wikitable"<br />
|-<br />
! # !! Parameter Name !! Description !! Supported Values !! Example<br />
|-<br />
| 1 || containerId ||View ID of the UI element used as player view in application. getId() method of View class can be used to get this value. ||A positive integer used to identify the view. || 2131558561<br />
|}<br />
<br />
<br />
==== iOS ====<br />
{| class="wikitable"<br />
|-<br />
! # !! Parameter Name !! Description !! Supported Values !! Example<br />
|-<br />
| 1 || containerId ||The tag of the UIView that represents the Player View ||The string value representing the NSInteger value with maximum value of NSIntegerMax that is related on 32- or 64-bit applications. || "100" <br> "2131558561"<br />
|}<br />
<br />
For iOS it is required to link additional frameworks that are needed for viewability engine:<br><br />
<code>JavaScriptCore.framework</code> <br><br />
<code>WebKit.framework</code><br />
<br />
The Nielsen AppSDK uses a tracking WebView (TWV) approach. For more information on Viewability, please refer to [https://engineeringportal.nielsen.com/docs/Implementing_Viewability_with_AppSDK Implementing Viewability with AppSDK.]<br />
--><br />
<br />
== APP SDK Error & Event Codes ==<br />
To view the Error and Event codes for iOS and Android, please review the [[APP SDK Event Codes|App SDK Event Code]] Reference page.<br />
<br />
== Simplified API Syntax ==<br />
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. <br />
<br />
Main API call for the new NielsenEventTracker API:<br />
<br />
<syntaxhighlight lang="objective-c"> <br />
- (void)trackEvent:(NSDictionary *)data;<br />
</syntaxhighlight><br />
<br />
=== Handling JSON Metadata ===<br />
Parameter “data” is a JSON object with many key-value pairs that holds all information required by SDK.<br />
<br />
Format of input object is the following:<br />
<syntaxhighlight lang="json"><br />
{ <br />
"event": <event identifier>,<br />
"type": <type of metadata>,<br />
"metadata":{ <br />
"content": <content metadata object>,<br />
"ad": <ad metadata object>,<br />
"static": <static metadata object><br />
},<br />
"playheadPosition":<playhead value | UTC>,<br />
"id3Data": <id3 payload>,<br />
}<br />
</syntaxhighlight><br />
<br><br />
=== Event Types ===<br />
The New API method supports the following event types:<br />
{| class="wikitable"<br />
!Key<br />
!Description<br />
|-<br />
|'''playhead'''||<br />
It is used to pass content, ad or static metadata, the current playhead value, UTC timestamp or id3 payload, ott information to the SDK.<br />
|-<br />
|'''pause'''||<br />
This event should be used to in the following cases:<br />
application enters background,<br />
any application interruptions,<br />
content playback is paused. (Pause is detected by SDK automatically only if the time gap between commands exceeds 30 minutes.)<br />
|-<br />
|'''complete'''||<br />
It is called when session is completed or ends.<br />
|-<br />
|'''adStop'''||<br />
Should be called at the end of each ad. This event type is required to handle the case when advertisements could not be distinguished, as its assetId is the same.<br />
|}<br />
<br><br />
DCR and DTVR require various levels of data. Please select the TAB of the product you are interested in reviewing.<br />
{{DCRDTVRTabs<br />
|DCR=<br />
=== Digital Content Ratings===<br />
<table><br />
<tr><br />
<th> Parameter<br />
</th><br />
<th> <b>Description</b><br />
</th><br />
<th> <b>Supported values</b><br />
</th><br />
<th> <b>Example</b><br />
</th></tr><br />
<tr><br />
<td> <b>event</b><br />
</td><br />
<td> Event identifier<br />
</td><br />
<td><br />
<p><code> String:<br />
playhead,pause,complete,<br />
adStop</code><br />
</p><br />
</td><br />
<td><syntaxhighlight lang="swift">"event":"playhead"</syntaxhighlight><br />
</td></tr><br />
<tr><br />
<td> <b>type</b><br />
</td><br />
<td> Determines the metadata object <br />
that should be used for crediting.<br />
</td><br />
<td><br />
<p><code> String:<br /><br />
content,<br />
ad,<br />
static</code><br />
</p><br />
</td><br />
<td><syntaxhighlight lang="swift">"type":"content"</syntaxhighlight><br />
</td></tr><br />
<tr><br />
<td> <b>metadata</b><br />
</td><br />
<td> Object that holds metadata values of specific types<br /><br />
<p><span style="color:blue"> Detailed in tables below</span><br />
</p><br />
</td><br />
<td> <code>Object</code><br />
</td><br />
<td><syntaxhighlight lang="swift"><br />
"metadata":{ <br />
"content": <content metadata object>,<br />
"ad": <ad metadata object>,<br />
"static": <static metadata object><br />
},<br />
</syntaxhighlight><br />
</td></tr><br />
<tr><br />
<td> <b>playheadPosition</b><br />
</td><br />
<td> Playhead value or UTC timestamp<br />
</td><br />
<td> <code>String</code><br />
</td><br />
<td><br />
<p>Position value is UTC timestamp:<br />
<code><br />
"playheadPosition":"1501225191747"</code><br />
</p><p>Position value is playhead:<br />
<code><br />
"playheadPosition":"10"</code><br />
</p><br />
</td></tr><br />
</table><br />
=== Content Metadata ===<br />
Content metadata sent for every playheadposition update.<br />
<table><br />
<tr><br />
<th> Keys </th><br />
<th> Description </th><br />
<th> Example </th><br />
<th> Required<br />
</th></tr><br />
<tr><br />
<td>'''assetName''' </td><br />
<td> name of program (100 character limit) </td><br />
<td> <code>"MyTest789"</code> </td><br />
<td> Yes<br />
</td></tr><br />
<tr><br />
<td> '''assetid''' </td><br />
<td> unique ID assigned to asset </td><br />
<td> custom </td><br />
<td> Yes<br />
</td></tr><br />
<tr><br />
<td> '''length''' </td><br />
<td> length of content in seconds </td><br />
<td> <code>seconds</code> (86400 for live stream) </td><br />
<td> Yes<br />
</td></tr><br />
<tr><br />
<td> '''program''' </td><br />
<td>name of program (100 character limit) </td><br />
<td> custom </td><br />
<td> Yes<br />
</td></tr><br />
<tr><br />
<td> '''segB''' </td><br />
<td> custom segment B +</td><br />
<td> custom </td><br />
<td><br />
</td></tr><br />
<tr><br />
<td> '''segC''' </td><br />
<td> custom segment C +</td><br />
<td> custom </td><br />
<td><br />
</td></tr><br />
<tr><br />
<td> '''title''' </td><br />
<td>name of program (100 character limit) </td><br />
<td> custom </td><br />
<td> Yes<br />
</td></tr><br />
<tr><br />
<td>'''type'''</td><br />
<td><code>'content', 'ad', 'static'</code></td><br />
<td> <code> 'content'</code> </td><br />
<td> Yes<br />
</td></tr><br />
<tr><br />
<td>'''section''' </td><br />
<td> Unique Value assigned to page/site section </td><br />
<td> HomePage </td><br />
<td> Yes<br />
</td></tr><br />
<tr><br />
<td> '''airdate''' </td><br />
<td> the airdate in the linear TV ++</td><br />
<td> YYYYMMDD HH24:MI:SS </td><br />
<td> Yes<br />
</td></tr><br />
<tr><br />
<td> '''isfullepisode''' </td><br />
<td> full episode flag </td><br />
<td> <code>"y"</code>- full episode, <code>"n"</code>- non full episode </td><br />
<td> Yes<br />
</td></tr><br />
<tr><br />
<td> '''crossId1''' </td><br />
<td> standard episode ID </td><br />
<td> custom </td><br />
<td> ✓<br />
</td></tr><br />
<tr><br />
<td> '''crossId2 '''</td><br />
<td> content originator (only required for distributors) </td><br />
<td> Nielsen provided </td><br />
<td><br />
</td></tr><br />
<tr><br />
<td> <b>adloadtype</b><br />
</td><br />
<td> linear vs dynamic ad model<br />
</td><br />
<td> 1=Linear<br />
2=Dynamic Ads<br />
</td><br />
<td>custom<br />
</td></tr><br />
</table><br />
+ '''Custom segments''' (segB and segC) can be used to aggregate video and/or static content within a single Brand to receive more granular reports within a brand.<br><br />
++ Acceptable '''Air Date''' Formats:<br />
<syntaxhighlight lang="json"><br />
YYYYMMDD HH24:MI:SS<br />
YYYYMMDDHH24MISS<br />
YYYY-MM-DDTHH:MI:SS<br />
YYYY-MM-DDHH:MI:SS <br />
YYYYMMDDHH:MI:SS<br />
MM-DD-YYYY<br />
YYYYMMDD HH:MI:SS<br />
</syntaxhighlight><br />
<br><br />
For USA all times should be EST, for all other countries Local Time.<br />
Below is a sample event for DCR. If no ad or static values, these can be left as blank/null.<br />
<syntaxhighlight lang="json"><br />
{ <br />
"event": "playhead",<br />
"type": "content",<br />
"metadata": { <br />
"content":{<br />
"assetName":"Big Buck Bunny",<br />
"assetid":"B66473",<br />
"length":"3600",<br />
"program":"MyProgram",<br />
"segB":"CustomSegmentValueB",<br />
"segC":"segmentC",<br />
"title":"S2,E3",<br />
"type":"content",<br />
"section":"cloudApi_app",<br />
"airdate":"20180120 10:00:00",<br />
"isfullepisode":"y",<br />
"crossId1":"Standard Episode ID",<br />
"crossId2" :"Content Originator",<br />
"adloadtype":"2"},<br />
"ad": {},<br />
"static": {}<br />
},<br />
"playheadPosition": "",<br />
}<br />
</syntaxhighlight><br />
<br />
|DTVR=<br />
=== Digital TV Ratings info ===<br />
<table><br />
<tr><br />
<th> Parameter<br />
</th><br />
<th> <b>Description</b><br />
</th><br />
<th> <b>Supported values</b><br />
</th><br />
<th> <b>Example</b><br />
</th></tr><br />
<tr><br />
<td> <b>event</b><br />
</td><br />
<td> Event identifier<br />
</td><br />
<td><br />
<p><code> String:<br />
playhead,pause,complete,<br />
adStop</code><br />
</p><br />
</td><br />
<td><syntaxhighlight lang="swift">"event":"playhead"</syntaxhighlight><br />
</td></tr><br />
<tr><br />
<td> <b>type</b><br />
</td><br />
<td> Determines the metadata object that should be used for crediting.<br />
</td><br />
<td><br />
<p><code> String:<br /><br />
content,<br />
ad,<br />
static</code><br />
</p><br />
</td><br />
<td><syntaxhighlight lang="swift">"type":"content"</syntaxhighlight><br />
</td></tr><br />
<tr><br />
<td> <b>adModel</b><br />
</td><br />
<td> linear vs dynamic ad model<br />
</td><br />
<td> 1=Linear<br />
2=Dynamic Ads<br />
</td><br />
<td>custom<br />
</td></tr><br />
<tr><br />
<td> <b>channelName</b><br />
</td><br />
<td> Any string representing the.channel/stream<br />
</td><br />
<td> <code>Object</code><br />
</td><br />
<td>custom<br />
</td></tr><br />
<tr><br />
<td> <b>playheadPosition</b><br />
</td><br />
<td> Playhead value or UTC timestamp<br />
</td><br />
<td> <code>String</code><br />
</td><br />
<td><br />
<p>Position value is UTC timestamp:<br />
<code><br />
"playheadPosition":"1501225191747"</code><br />
</p><p>Position value is playhead:<br />
<code><br />
"playheadPosition":"10"</code><br />
</p><br />
</td></tr><br />
<tr><br />
<td> <b>id3Data</b><br />
</td><br />
<td> Nielsen ID3 payload string<br />
</td><br />
<td> <code>Object</code><br />
</td><br />
<td><br />
<p><code><br />
id3Data: www.nielsen.com/065H2g6E7ZyQ5UdmMAbbpg<br />
==/_EMc37zfVgq_8KB7baUYfg==/ADQCAmgV1Xyvnynyg60<br />
kZO_Ejkcn2KLSrTzyJpZZ-QeRn8GpMGTWI7-HrEKzghxyzC<br />
yBEoIDv2kA2g1QJmeYOl5GnwfrLDVK2bNLTbQxr1z9VBfxa<br />
hBcQP5tqbjhyMzdVqrMKuvvJO1jhtSXa9AroChb11ZUnG1W<br />
VJx2O4M=/33648/22847/00</code><br />
</p><br />
<tr><br />
<br />
</td></tr><br />
</table><br />
Below is a sample event for DTVR. If no ad or static values, these can be left as blank/null.<br />
<syntaxhighlight lang="json"><br />
{ <br />
"event": "playhead",<br />
"type": "content",<br />
"metadata": { <br />
"content":{<br />
"adModel":"1",<br />
"channelname":"channel1"<br />
},<br />
"ad": {},<br />
"static": {}<br />
},<br />
"playheadPosition": "",<br />
"id3Data": "www.nielsen.com/065H2g6E7ZyQ5UdmMAbbpg==/_<br />
EMc37zfVgq_8KB7baUYfg==/ADQCAmgV1Xyvnynyg60kZO_Ejkcn<br />
2KLSrTzyJpZZ-QeRn8GpMGTWI7-HrEKzghxyzCyBEoIDv2kA2g1Q<br />
JmeYOl5GnwfrLDVK2bNLTbQxr1z9VBfxahBcQP5tqbjhyMzdVqrMK<br />
uvvJO1jhtSXa9AroChb11ZUnG1WVJx2O4M=/33648/22847/00"<br />
}<br />
</syntaxhighlight><br />
|DCRDTVR=<br />
=== Applies to DCR and DTVR ===<br />
<table><br />
<tr><br />
<th> Parameter<br />
</th><br />
<th> <b>Description</b><br />
</th><br />
<th> <b>Supported values</b><br />
</th><br />
<th> <b>Example</b><br />
</th></tr><br />
<tr><br />
<td> <b>event</b><br />
</td><br />
<td> Event identifier<br />
</td><br />
<td><br />
<p><code> String:<br />
playhead,<br />
pause,<br />
complete,<br />
adStop</code><br />
</p><br />
</td><br />
<td><syntaxhighlight lang="swift">"event":"playhead"</syntaxhighlight><br />
</td></tr><br />
<tr><br />
<td> <b>type</b><br />
</td><br />
<td> Determines the metadata object that should be used for crediting.<br />
</td><br />
<td><br />
<p><code> String:<br /><br />
content,<br />
ad,<br />
static</code><br />
</p><br />
</td><br />
<td><syntaxhighlight lang="swift">"type":"content"</syntaxhighlight><br />
</td></tr><br />
<tr><br />
<td> <b>metadata</b><br />
</td><br />
<td> Object that holds metadata values of specific types<br /><br />
<p><span style="color:blue"> Detailed in tables below</span><br />
</p><br />
</td><br />
<td> <code>Object</code><br />
</td><br />
<td><syntaxhighlight lang="swift"><br />
"metadata":{ <br />
"content": <content metadata object>,<br />
"ad": <ad metadata object>,<br />
"static": <static metadata object><br />
},<br />
</syntaxhighlight><br />
</td></tr><br />
<tr><br />
<td> <b>playheadPosition</b><br />
</td><br />
<td> Playhead value or UTC timestamp<br />
</td><br />
<td> <code>String</code><br />
</td><br />
<td><br />
<p>Position value is UTC timestamp:<br />
<code><br />
"playheadPosition":"1501225191747"</code><br />
</p><p>Position value is playhead:<br />
<code><br />
"playheadPosition":"10"</code><br />
</p><br />
</td></tr><br />
<tr><br />
<td> <b>id3Data</b><br />
</td><br />
<td> Nielsen ID3 payload string<br />
</td><br />
<td> <code>Object</code><br />
</td><br />
<td><br />
<p><code><br />
id3Data: www.nielsen.com/065H2g6E7ZyQ5UdmMAbbpg<br />
==/_EMc37zfVgq_8KB7baUYfg==/ADQCAmgV1Xyvnynyg60<br />
kZO_Ejkcn2KLSrTzyJpZZ-QeRn8GpMGTWI7-HrEKzghxyzC<br />
yBEoIDv2kA2g1QJmeYOl5GnwfrLDVK2bNLTbQxr1z9VBfxa<br />
hBcQP5tqbjhyMzdVqrMKuvvJO1jhtSXa9AroChb11ZUnG1W<br />
VJx2O4M=/33648/22847/00</code><br />
</p><br />
</td></tr><br />
<tr><br />
<td> <b>ottData</b><br />
</td><br />
<td> Object that holds ott information<br />
</td><br />
<td> <code>Object</code><br />
</td><br />
<td><syntaxhighlight lang="swift"><br />
ottData:{<br />
ottStatus:1,<br />
ottType:casting,<br />
ottDevice:chromecast,<br />
ottDeviceName:Google ChromeCast,<br />
ottDeviceID:1234,<br />
ottDeviceModel:ChromeCast,<br />
ottDeviceVersion:1.0.0<br />
}<br />
</syntaxhighlight><br />
</td></tr><br />
</table><br />
=== Content Metadata ===<br />
Content metadata sent for every playheadposition update.<br />
<table><br />
<tr><br />
<th> Keys </th><br />
<th> Description </th><br />
<th> Example </th><br />
<th> Required<br />
</th></tr><br />
<tr><br />
<td> '''length''' </td><br />
<td> length of content in seconds </td><br />
<td> <code>seconds</code> (86400 for live stream) </td><br />
<td> Yes<br />
</td></tr><br />
<tr><br />
<td>'''type'''</td><br />
<td><code>'content', 'ad', 'static'</code></td><br />
<td> <code> 'content'</code> </td><br />
<td> Yes<br />
</td></tr><br />
<tr><br />
<td> <b>adModel</b><br />
</td><br />
<td> linear vs dynamic ad model<br />
</td><br />
<td> 1=Linear<br />
2=Dynamic Ads<br />
</td><br />
<td>custom<br />
</td></tr><br />
<tr><br />
<td> <b>adloadtype</b><br />
</td><br />
<td> DCR Ad Model<br />
</td><br />
<td> 1=Linear<br />
2=Dynamic Ads<br />
</td><br />
<td>custom<br />
</td></tr><br />
</table><br />
+ '''Custom segments''' (segB and segC) can be used to aggregate video and/or static content within a single Brand to receive more granular reports within a brand.<br><br />
++ Acceptable '''Air Date''' Formats:<br />
<syntaxhighlight lang="json"><br />
YYYYMMDD HH24:MI:SS<br />
YYYYMMDDHH24MISS<br />
YYYY-MM-DDTHH:MI:SS<br />
YYYY-MM-DDHH:MI:SS <br />
YYYYMMDDHH:MI:SS<br />
MM-DD-YYYY<br />
YYYYMMDD HH:MI:SS<br />
</syntaxhighlight><br />
<br><br />
Below is a sample event for DCR/DTVR joint integration. If no ad or static values, these can be left as blank/null.<br />
<syntaxhighlight lang="json"><br />
{ <br />
"event": "playhead",<br />
"type": "content",<br />
"metadata": { <br />
"content":{<br />
"type":"content",<br />
"length":"86400",<br />
"adModel":"1",<br />
"adloadtype":"1"},<br />
"ad": {},<br />
"static": {}<br />
},<br />
"playheadPosition": "",<br />
"id3Data": "www.nielsen.com/065H2g6E7ZyQ5UdmMAbbpg==/_<br />
EMc37zfVgq_8KB7baUYfg==/ADQCAmgV1Xyvnynyg60kZO_Ejkcn<br />
2KLSrTzyJpZZ-QeRn8GpMGTWI7-HrEKzghxyzCyBEoIDv2kA2g1Q<br />
JmeYOl5GnwfrLDVK2bNLTbQxr1z9VBfxahBcQP5tqbjhyMzdVqrMK<br />
uvvJO1jhtSXa9AroChb11ZUnG1WVJx2O4M=/33648/22847/00"<br />
}<br />
</syntaxhighlight><br />
}}<br />
<br />
=== Ad Metadata ===<br />
The ad metadata (if applicable) should be passed for each individual ad, if ads are available during or before the stream begins.<br />
<br/><br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Example<br />
|-<br />
| assetid || unique ID assigned to ad || custom <br>(no [[Special Characters]]) || <code>'AD1234'</code><br />
|-<br />
| title || unique name assigned to ad || custom ||<code>'ADtitle'</code><br />
|-<br />
|adldx || Ad Index (*See Note below*) || custom || <code> "66478364" </code><br />
|-<br />
| type || type of ad || 'preroll', 'midroll', or 'postroll' || <code>'preroll'</code><br />
|-<br />
|length || length of ad || In Seconds ||<code> '20' </code><br />
|}<br />
=== Ad Metadata Sample ===<br />
<syntaxhighlight lang="json"><br />
{<br />
"ad": {<br />
"assetid":"AD12345",<br />
"title":"ADTestTitle",<br />
"adldx":"1",<br />
"type":"preroll",<br />
"length":"20"<br />
},<br />
}<br />
</syntaxhighlight><br />
<br />
=== Managing Ads ===<br />
If there is an Ad block within the playing content (such as a midroll) you need to:<br />
* Reset the playhead position to 0 for each ad.<br />
* Call the '''adStop''' event at the end of each ad or increment the adldx<br />
<br />
The Simplified SDK will can automatically detect the change from ad to content, or even ad to ad if the assetID changes; however, there could be situations where the same ad is played back to back. You can either increment/change the '''adldx value''', and/or call adStop at the end of each Ad.<br />
<br />
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 Nielsen Simplified API will support a new parameter for ad metadata: '''adIdx.''' This parameter is just 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.<br />
<br />
<syntaxhighlight lang="swift"><br />
// Example of passing both values<br />
self.data.updateValue("adStop", forKey: "event")<br />
self.data.updateValue("223", forKey: "adldx")<br />
self.nielsenEventTracker.trackEvent(data)<br />
</syntaxhighlight><br />
<br />
=== Static Metadata ===<br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| type || type identifier || <code> "static" </code> || ✓<br />
|-<br />
| assetid || unique ID assigned for each article/section || custom || ✓<br />
|-<br />
|section || Unique Value assigned to page/site section || HomePage || Yes<br />
|-<br />
| segA ||name of program (25 character limit) || custom || Yes<br />
|-<br />
| segB || custom segment B || custom || <br />
|-<br />
| segC || custom segment C || custom || <br />
|-<br />
|}<br />
<syntaxhighlight lang="json"><br />
{<br />
"static":<br />
{<br />
"type": "static",<br />
"section": "homeSection",<br />
"assetid": "AID885-9984",<br />
"segA": "CustomSegmentValueA",<br />
"segB": "CustomSegmentValueB",<br />
"segC": "CustomSegmentValueC",<br />
}<br />
},<br />
</syntaxhighlight><br />
<br />
=== Putting it all together ===<br />
{{ExampleCode|<br />
|Swift = <br />
<syntaxhighlight lang="swift"><br />
func loadPreRollAd() -> [String : Any] {<br />
<br />
//Loading Ad data<br />
<br />
url = NSURL(string: "http://www.nielseninternet.com/NWCC-3002/prog_index.m3u8")<br />
<br />
let content = [<br />
"assetName":"Big Buck Bunny",<br />
"assetid":"B66473",<br />
"length":"3600",<br />
"program":"MyProgram",<br />
"segB":"CustomSegmentValueB",<br />
"segC":"segmentC",<br />
"title":"S2,E3",<br />
"type":"content",<br />
"section":"app_Mainpage",<br />
"airdate":"20180120 10:00:00",<br />
"isfullepisode":"y",<br />
"crossId1":"Standard Episode ID",<br />
"crossId2" :"Content Originator",<br />
"adloadtype":"2"<br />
]<br />
<br />
let staticObj = [<br />
"type":"static",<br />
"section":"homeSection",<br />
"segA":"CustomSegmentValueA",<br />
"segB":"CustomSegmentValueB",<br />
"segC":"CustomSegmentValueC"<br />
]<br />
<br />
let ad = [<br />
"assetid":"AD12345",<br />
"title":"ADTestTitle",<br />
"adldx":"1",<br />
"type":"preroll",<br />
"length":"20"<br />
]<br />
<br />
let metadata = [<br />
"content" : content,<br />
"ad" : ad,<br />
"static" : staticObj<br />
] as [String : Any]<br />
<br />
<br />
let data = [<br />
"metadata" : metadata,<br />
"event": "playhead",<br />
"playheadPosition": "0",<br />
"type": "ad",<br />
] as [String : Any]<br />
<br />
return data <br />
}<br />
</syntaxhighlight> <br />
|Objective C = <syntaxhighlight lang="objective-c"> <br />
#import <Foundation/Foundation.h><br />
#import "SDKMethods.h"<br />
<br />
@implementation SDKMethods<br />
//Loading content Data<br />
- (NSDictionary *)loadContentData<br />
{<br />
- (NSDictionary *)loadPreRollAd<br />
{<br />
self.url = [NSURL URLWithString:@"http://www.nielseninternet.com/NWCC-3002/prog_index.m3u8"];<br />
<br />
//We should pass content dictionary also in Ad video.<br />
NSDictionary *content = @{ @"assetName":@"ChromeCast1",<br />
@"assetid":@"C77664",<br />
@"length":@"3600",<br />
@"program":@"MyProgram",<br />
@"segB":@"CustomSegmentValueB",<br />
@"segC":@"segmentC",<br />
@"title":@"S2,E3",<br />
@"type":@"content",<br />
@"section":@"app_Mainpage",<br />
@"airdate":@"20180120 10:00:00",<br />
@"isfullepisode":@"y",<br />
@"adloadtype":@"2",<br />
@"channelName":@"My Channel 1",<br />
@"pipMode":@"false" };<br />
<br />
NSDictionary *ad = @{ @"assetid":@"AD12345",<br />
@"title":@"ADTestTitle",<br />
@"type":@"preroll",<br />
@"length":@"20" };<br />
<br />
NSDictionary *staticObj = @{ @"type":@"static",<br />
@"section":@"homeSection",<br />
@"segA":@"CustomSegmentValueA",<br />
@"segB":@"CustomSegmentValueB",<br />
@"segC":@"CustomSegmentValueC" };<br />
<br />
//static data should be empty in Ad video<br />
NSDictionary *metadata = @{ @"content" : content,<br />
@"ad" : ad,<br />
@"static" : @staticObj };<br />
<br />
NSDictionary *data = @{ @"metadata" : metadata,<br />
@"event": @"playhead",<br />
@"type": @"ad",<br />
@"playheadPosition": @"0" };<br />
<br />
return data;<br />
}<br />
</syntaxhighlight><br />
}}<br />
<br />
== JSON examples ==<br />
Additional JSON examples such as:<br />
<br />
* [[Digital_Measurement_Simplified_SDK_Supplements#Static_Metadata|Static Metadata Only]]<br />
* [[Digital_Measurement_Simplified_SDK_Supplements#Ad_Metadata|Ad Metadata Example]]<br />
* [[Digital_Measurement_Simplified_SDK_Supplements#ID3_Payload|ID3 payload for DTVR]]<br />
* [[Digital_Measurement_Simplified_SDK_Supplements#Pause_Event|Sample Pause Event]]<br />
* [[Digital_Measurement_Simplified_SDK_Supplements#Complete_Event|Sample Complete Event]]<br />
<br />
== Handling Foreground and Background states ==<br />
For iOS, background/foreground detection is handled by the app lifecylce APIs which are provided by [https://developer.apple.com/library/content/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/TheAppLifeCycle/TheAppLifeCycle.html Apple:]<br />
<br />
Foreground/Background state measurement is a requirement of Nielsen AppSDK implementation which is especially crucial for static measurement.<br />
<br />
== Privacy and Opt-Out ==<br />
There are three primary methods for implementing user Opt-out preferences:<br />
# '''[[#OS-level_Opt-out|OS-level Opt-out]]''' - managed by ''Limit Ad Tracking'' setting on device ('''preferred approach''').<br />
# '''[[#Legacy_Opt-out|Legacy Opt-out]]''' - Direct call to SDK; used only for older versions of Nielsen iOS SDK (< 5.1.1.18)<br />
# '''[[#App_Level_Opt_Out|App Level Opt-Out]]''' - Where [https://developer.apple.com/documentation/adsupport Ad Framework] cannot be leveraged<br />
<br />
=== OS-level Opt-out ===<br />
''OS-level Opt-out'' method available on Nielsen iOS '''SDK Versions 5.1.1.18 and above'''.<br />
<br />
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.<br />
<br />
=== Legacy Opt-out ===<br />
The ''Legacy opt-out'' method is only necessary for Nielsen iOS '''SDK versions less than 5.1.1.18'''.<br />
<br />
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.<br />
<br />
==== The legacy opt-out method works as follows: ====<br />
* Get the legacy Nielsen opt-out URL via [[optOutURL]]<br />
* Display a WebView element whose loadUrl is set to the value obtained from [[optOutURL]]<br />
* Detect if the WebView URL changes to a special URL that indicates Opt-in, or Opt-out and close the WebView<br />
** Opt-out if the WebView URL = <code>nielsenappsdk://1</code><br />
** Opt-in if the WebView URL = <code>nielsenappsdk://0</code><br />
* Pass the detected URL to the [[userOptOut]] function<br />
** Example: <syntaxhighlight lang=swift>NielsenAppApi?.userOptOut("nielsenappsdk://1"); // User opt-out</syntaxhighlight><br />
<br />
==== Legacy Opt-out example code ====<br />
<br />
<syntaxhighlight lang="swift"><br />
var webView: WKWebView!<br />
var NIELSEN_URL_OPT_OUT : String = "nielsenappsdk://1"<br />
var NIELSEN_URL_OPT_IN : String = "nielsenappsdk://0"<br />
<br />
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {<br />
<br />
let urlStr = navigationAction.request.url?.absoluteString<br />
<br />
if(urlStr == NIELSEN_URL_OPT_OUT || urlStr == NIELSEN_URL_OPT_IN){<br />
let appApi = self.nielsenApi<br />
appApi?.userOptOut(urlStr)<br />
decisionHandler(.allow)<br />
<br />
}else{<br />
decisionHandler(.cancel)<br />
}<br />
}<br />
</syntaxhighlight><br />
<br />
=== 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<br />
<br />
=== Required Privacy Links ===<br />
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.<br />
<br />
In addition, the following text must be included in your app store description.<br />
<blockquote><br />
'''"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><br />
<br />
==== Webview Example ====<br />
The below code is an example of displaying the Nielsen Privacy page to the user.<br />
<syntaxhighlight lang="swift"><br />
import UIKit<br />
import WebKit<br />
import NielsenAppApi<br />
<br />
class OptOutVC: UIViewController, NielsenAppApiDelegate, WKNavigationDelegate {<br />
<br />
var webView: WKWebView!<br />
var nielsenApi: NielsenAppApi!<br />
<br />
override func loadView() {<br />
webView = WKWebView()<br />
webView.navigationDelegate = self<br />
view = webView<br />
}<br />
<br />
override func viewDidLoad() {<br />
super.viewDidLoad()<br />
self.view.backgroundColor = UIColor(patternImage: UIImage(named: "new_ios_bg.png")!) <br />
self.nielsenApi = NielsenInit.createNielsenApi(delegate: self)<br />
<br />
if let appApi = self.nielsenApi {<br />
//Getting the optPut URL from SDK<br />
if let url = URL(string: appApi.optOutURL) {<br />
webView.load(URLRequest(url: url))<br />
webView.allowsBackForwardNavigationGestures = true<br />
}<br />
}<br />
}<br />
<br />
}<br />
</syntaxhighlight><br />
<br><br />
=== App Level Opt Out ===<br />
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:<br />
* Implement the UIWebView delegate method to open the Nielsen Privacy web page<br />
* Capture user's selection<br />
* Pass the selection back to the SDK via the <code>userOptOut</code>.<br />
<br />
==== Capture and forward user selection ====<br />
<syntaxhighlight lang="objective-c">-(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType<br />
{<br />
NSString *command = [NSString stringWithFormat:@”%@”,request.URL];<br />
if ([command isEqualToString:kNielsenWebClose]) {<br />
// Close the WebView<br />
[self performSelector:@selector(closeOptOutView) withObject:nil afterDelay:0];<br />
return NO;<br />
}<br />
// Retrieve next URL if it’s not opt-in/out selection<br />
return (![nAppApiObject userOptOut:command]);<br />
}</syntaxhighlight><br />
<br />
*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.<br />
**<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.<br />
<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><br />
<br />
== Example Code ==<br />
=== Putting it all together ===<br />
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.<br />
{{ExampleCode|<br />
|Swift = <br />
[[File:iphonescreenshot.png|thumb]]<br />
=== Swift Version 4 Code Example ===<br />
Select the below link to download the sample files <br><br />
[https://engineeringportal.nielsen.com/w/downloads/digital/sampleapplications/TrackEvent-SDKSwift.zip Download Project Files]<br />
==== NielsenInit.swift ====<br />
<br />
<syntaxhighlight lang="swift"><br />
// This is sample code of a very basic implementation of the Nielsen 'Simplified API'<br />
// This code is for educational purposes only<br />
//<br />
import Foundation<br />
import NielsenAppApi<br />
<br />
class NielsenInit : NSObject {<br />
class func createEventTracker(delegate: NielsenEventTrackerDelegate) -> NielsenEventTracker?{<br />
<br />
//Initialising the NielsenEventTracker class by passing app information which returns the instance of NielsenEventTracker.<br />
<br />
var nielsenEventTracker: NielsenEventTracker?<br />
<br />
let appInformation = [<br />
<br />
"appid": "PDA7D5EE6-B1B8-4123-9277-2A788XXXXXXX",<br />
"appversion": "1.0",<br />
"appname": "Amazing app",<br />
"sfcode": "dcr",<br />
"nol_devDebug": "DEBUG",<br />
"containerId": String(containerId)<br />
]<br />
<br />
nielsenEventTracker = NielsenEventTracker(appInfo:appInformation, delegate:delegate)<br />
return nielsenEventTracker<br />
}<br />
}<br />
</syntaxhighlight><br />
<br />
==== SDKMethods.swift ====<br />
<syntaxhighlight lang="swift"><br />
<br />
import Foundation<br />
<br />
class SDKMethods : NSObject {<br />
<br />
var nielsenApi : NielsenAppApi!<br />
var url = NSURL(string: "")<br />
var content : NSDictionary!<br />
<br />
//Loading Static Data<br />
func loadStatic() -> [String : Any] {<br />
<br />
let staticObj = [<br />
"type":"static",<br />
"section":"homeSection",<br />
"segA":"CustomSegmentValueA",<br />
"segB":"CustomSegmentValueB",<br />
"segC":"CustomSegmentValueC"]<br />
<br />
<br />
let metadata = [<br />
"content" : [String:String](),<br />
"ad" : [String:String](),<br />
"static" : staticObj ] as [String : Any]<br />
<br />
let data = [<br />
"metadata" : metadata,<br />
"event": "playhead",<br />
"type": "static",<br />
"playheadPosition": "0" ] as [String : Any]<br />
<br />
return data<br />
}<br />
<br />
//Loading content Data<br />
func loadContent() -> [String : Any] {<br />
<br />
url = NSURL(string: "http://www.nielseninternet.com/NielsenConsumer/prog_index.m3u8")<br />
<br />
let content = [<br />
"assetName":"ChromeCast1",<br />
"assetid":"C77664",<br />
"length":"3600",<br />
"program":"MyProgram",<br />
"segB":"CustomSegmentValueB",<br />
"segC":"segmentC",<br />
"title":"S2,E3",<br />
"type":"content",<br />
"section":"myApi_app",<br />
"airdate":"20180120 10:00:00",<br />
"isfullepisode":"y",<br />
"adloadtype":"2",<br />
"channelName":"My Channel 1",<br />
"pipMode":"false" ]<br />
<br />
//Ad data,static data should be empty in content video dictionary<br />
let metadata = [<br />
"content" : content,<br />
"ad" : [String:String](),<br />
"static" : [String:String]() ] as [String : Any]<br />
<br />
let data = [<br />
"metadata" : metadata,<br />
"event": "playhead",<br />
"type":"content",<br />
"playheadPosition": "0" ] as [String : Any]<br />
<br />
return data<br />
<br />
}<br />
}<br />
</syntaxhighlight><br />
==== ViewController.swift ====<br />
<syntaxhighlight lang="swift"><br />
<br />
import UIKit<br />
import AVKit<br />
import CoreLocation<br />
import AdSupport<br />
import AVFoundation<br />
import NielsenAppApi<br />
<br />
class ViewController: UIViewController, NielsenEventTrackerDelegate, AVPlayerViewControllerDelegate {<br />
<br />
var videoType : Int!<br />
var player : AVPlayer!<br />
var playerController : AVPlayerViewController!<br />
var sdkMethods : SDKMethods!<br />
var nielsenEventTracker : NielsenEventTracker!<br />
<br />
var data : [String : Any]!<br />
var timeObserver: Any!<br />
var totalVideosPlayed = 0<br />
var totalVideos : Int!<br />
<br />
override func viewDidLoad() {<br />
super.viewDidLoad()<br />
<br />
//Mark: In NielsenInit class we are initialising the NielsenEventTracker.<br />
<br />
//Getting the instance of NielsenEventTracker<br />
self.nielsenEventTracker = NielsenInit.createEventTracker(delegate: self)<br />
<br />
//Mark: In SDKMethods class we wrote methods which creates content,Ad objects<br />
sdkMethods = SDKMethods()<br />
<br />
if(videoType == Constants.onlyContent){<br />
//loading video content data<br />
self.data = sdkMethods.loadContent()<br />
}else{<br />
//loading Ad data<br />
self.data = sdkMethods.loadPreRollAd()<br />
}<br />
<br />
setPlayer()<br />
setPlayHeadPosition()<br />
<br />
//Setting observer to know the completion of video<br />
setVideoFinishObserver()<br />
}<br />
<br />
override func viewDidAppear(_ animated: Bool) {<br />
//loading static data<br />
let staticData = sdkMethods.loadStatic()<br />
<br />
//sending static data to SDK.<br />
self.nielsenEventTracker.trackEvent(staticData)<br />
}<br />
<br />
func setPlayer() {<br />
<br />
//creating player<br />
player = AVPlayer.init(url: sdkMethods.url! as URL)<br />
playerController = AVPlayerViewController()<br />
playerController.view.frame = CGRect(x:0 , y:100, width: self.view.frame.width, height: 300)<br />
playerController.player = player;<br />
playerController.showsPlaybackControls = true;<br />
playerController.delegate = self;<br />
<br />
//Adding observer to player to track play,pause and reverse<br />
player.addObserver(self, forKeyPath: "rate", options: NSKeyValueObservingOptions.new, context: nil)<br />
player.play()<br />
<br />
self.addChildViewController(playerController)<br />
self.view.addSubview(playerController.view)<br />
}<br />
<br />
func setPlayHeadPosition() {<br />
<br />
//Setting play head position<br />
let timeInterval : CMTime = CMTimeMakeWithSeconds(1.0, 10)<br />
playerController.player?.addPeriodicTimeObserver(forInterval: timeInterval, queue: DispatchQueue.main) {(elapsedTime: CMTime) -> Void in<br />
<br />
let time : Float64 = self.playerController.player!.currentTime().seconds;<br />
let pos = Int64(time);<br />
let playHeadPos = String(pos)<br />
<br />
//updating playHead position in dictionary.<br />
self.data.updateValue(playHeadPos, forKey: "playheadPosition")<br />
<br />
//Sending data dictionary to SDK with updated playHead position.<br />
self.nielsenEventTracker.trackEvent(self.data)<br />
}<br />
}<br />
<br />
func setVideoFinishObserver() {<br />
<br />
//observer fires on completion of Video<br />
NotificationCenter.default.addObserver(self, selector: #selector(playerDidFinishPlaying), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: playerController.player?.currentItem)<br />
}<br />
<br />
//rate 0.0 = Video Pause or stopped<br />
//rate 1.0 = Video played or resumed<br />
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {<br />
if keyPath == "rate" {<br />
if let rate = change?[NSKeyValueChangeKey.newKey] as? Float {<br />
<br />
if rate == 0.0 {<br />
print("Playback stopped")<br />
<br />
//on video pause, updating event as pause in dictionary<br />
self.data.updateValue("pause", forKey: "event")<br />
<br />
//sending the dictionary to SDK with "pause" event.<br />
self.nielsenEventTracker.trackEvent(self.data)<br />
}<br />
if rate == 1.0 {<br />
print("normal playback")<br />
<br />
//On Play resume setting event as Playhead<br />
self.data.updateValue("playhead", forKey: "event")<br />
}<br />
}<br />
}<br />
}<br />
<br />
override func viewDidDisappear(_ animated: Bool) {<br />
<br />
//on moving to other screen, updating event as pause in dictionary<br />
self.data.updateValue("pause", forKey: "event")<br />
<br />
player.rate = 0<br />
player.pause()<br />
}<br />
<br />
@objc func playerDidFinishPlaying(note: NSNotification) {<br />
<br />
self.player?.removeObserver(self, forKeyPath: "rate")<br />
<br />
//As 1 video completed playing, incrementing the variable value.<br />
totalVideosPlayed += 1<br />
<br />
if(videoType == Constants.onlyContent || totalVideosPlayed == totalVideos){<br />
//When content video completes or total videos finishs, let's send complete event to SDK<br />
sendCompleteEventToSDK()<br />
}else{<br />
//On completion of Ad updating "adStop" event to SDK.<br />
self.data.updateValue("adStop", forKey: "event")<br />
<br />
//sending "adStop" event to SDK.<br />
self.nielsenEventTracker.trackEvent(data)<br />
}<br />
<br />
//Checking if total videos played or not.<br />
if(totalVideosPlayed != totalVideos){<br />
<br />
//Checking if videoType is contentWithOneAd, then after completion of Ad, will play the content video.<br />
if(videoType == Constants.contentWithOneAd){<br />
<br />
//loading video content data<br />
self.data = sdkMethods.loadContent()<br />
}else if(videoType == Constants.contentWithTwoAds){<br />
if(totalVideosPlayed == 1){<br />
<br />
//loading 2nd Ad data<br />
self.data = sdkMethods.loadMidRollAd()<br />
}else{<br />
<br />
//loading video content data<br />
self.data = sdkMethods.loadContent()<br />
}<br />
}<br />
setPlayer()<br />
<br />
//Adding observer to player to check is buffering finished<br />
self.timeObserver = player.addPeriodicTimeObserver(forInterval: CMTime(value: 1, timescale: 3), queue: DispatchQueue.main) { [weak self] time in<br />
<br />
//checking the video player status<br />
self?.handlePlayerStatus(time: time)<br />
self?.setPlayHeadPosition()<br />
<br />
//Setting observer to know the completion of video<br />
self?.setVideoFinishObserver()<br />
<br />
}<br />
}<br />
}<br />
<br />
func handlePlayerStatus(time: CMTime) {<br />
if player.status == .readyToPlay {<br />
<br />
// buffering is finished, setting event as Playhead<br />
self.data.updateValue("playhead", forKey: "event")<br />
player.removeTimeObserver(self.timeObserver)<br />
}<br />
if player.status == .unknown{<br />
print("Buffering")<br />
}<br />
}<br />
<br />
func sendCompleteEventToSDK(){<br />
<br />
//onCompletion of video, updating event as complete in dictionary<br />
self.data.updateValue("complete", forKey: "event")<br />
<br />
//sending the dictionary to SDK with "complete" event.<br />
self.nielsenEventTracker.trackEvent(self.data)<br />
}<br />
<br />
deinit {<br />
<br />
print("Remove NotificationCenter Deinit")<br />
NotificationCenter.default.removeObserver(self)<br />
}<br />
}<br />
<br />
<br />
</syntaxhighlight><br />
<br />
|Objective C = <br />
[[File:iphonescreenshot.png|thumb]]<br />
=== Objective-C Code Example ===<br />
Select the below link to download the sample files <br><br />
[https://engineeringportal.nielsen.com/w/downloads/digital/sampleapplications/TrackEvent-SDKObjC.zip Download Project Files]<br />
==== NielsenInit.m ====<br />
<syntaxhighlight lang="Objective-C"><br />
// NielsenInit.m<br />
// VideoPlayerAppObjC<br />
// This is sample code of a very basic implementation of the Nielsen 'Simplified API'<br />
// This code is for educational purposes only<br />
<br />
<br />
#import "NielsenInit.h"<br />
#import <NielsenAppApi/NielsenEventTracker.h><br />
<br />
@implementation NielsenInit<br />
<br />
+ (NielsenEventTracker *)createNielsenEventTrackerWithDelegate:(id<NielsenEventTrackerDelegate>)delegate<br />
{<br />
//Initialising the NielsenEventTracker class by passing app information which returns the instance of NielsenEventTracker.<br />
<br />
NSDictionary *appInformation = @{ @"appid": @"PDA7D5EE6-B1B8-4123-9277-2A788BC653CA",<br />
@"appversion": @"1.0",<br />
@"appname": @"Abdul's Objc Test app",<br />
@"sfcode": @"dcr",<br />
@"ccode": @"123",<br />
@"dma":@"456",<br />
@"uoo":@"0",<br />
@"nol_devDebug": @"INFO",<br />
@"containerId": @"1" };<br />
<br />
return [[NielsenEventTracker alloc] initWithAppInfo:appInformation delegate:delegate];<br />
}<br />
<br />
<br />
@end<br />
</syntaxhighlight><br />
<br />
==== NielsenInit.h ====<br />
<syntaxhighlight lang="Objective-C"><br />
#import <Foundation/Foundation.h><br />
<br />
@class NielsenEventTracker;<br />
@protocol NielsenEventTrackerDelegate;<br />
<br />
@interface NielsenInit : NSObject<br />
<br />
+ (NielsenEventTracker *)createNielsenEventTrackerWithDelegate:(id<NielsenEventTrackerDelegate>)delegate;<br />
<br />
@end<br />
</syntaxhighlight><br />
<br />
==== SDKMethods.m ====<br />
<syntaxhighlight lang="Objective-C"><br />
#import <Foundation/Foundation.h><br />
#import "SDKMethods.h"<br />
<br />
<br />
@implementation SDKMethods<br />
<br />
//Loading content Data<br />
- (NSDictionary *)loadContentData<br />
{<br />
<br />
self.url = [NSURL URLWithString:@"http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerEscapes.mp4"];<br />
<br />
NSDictionary *content = @{ @"assetName":@"ChromeCast1",<br />
@"assetid":@"C77664",<br />
@"length":@"3600",<br />
@"program":@"MyProgram",<br />
@"segB":@"CustomSegmentValueB",<br />
@"segC":@"segmentC",<br />
@"title":@"S2,E3",<br />
@"type":@"content",<br />
@"section":@"cloudApi_app",<br />
@"airdate":@"20180120 10:00:00",<br />
@"isfullepisode":@"y",<br />
@"adloadtype":@"2",<br />
@"channelName":@"My Channel 1",<br />
@"pipMode":@"false" };<br />
<br />
//Ad data,static data should be empty in content video dictionary<br />
NSDictionary *metadata = @{ @"content" : content,<br />
@"ad" : @{},<br />
@"static" : @{} };<br />
<br />
NSDictionary *data = @{ @"metadata" : metadata,<br />
@"event": @"playhead",<br />
@"type": @"content",<br />
@"playheadPosition": @"0" };<br />
<br />
<br />
<br />
return data;<br />
}<br />
<br />
//Loading Ad data<br />
- (NSDictionary *)loadPreRollAd<br />
{<br />
self.url = [NSURL URLWithString:@"http://www.nielseninternet.com/NWCC-3002/prog_index.m3u8"];<br />
<br />
//We should pass content dictionary also in Ad video.<br />
NSDictionary *content = @{ @"assetName":@"ChromeCast1",<br />
@"assetid":@"C77664",<br />
@"length":@"3600",<br />
@"program":@"MyProgram",<br />
@"segB":@"CustomSegmentValueB",<br />
@"segC":@"segmentC",<br />
@"title":@"S2,E3",<br />
@"type":@"content",<br />
@"section":@"cloudApi_app",<br />
@"airdate":@"20180120 10:00:00",<br />
@"isfullepisode":@"y",<br />
@"adloadtype":@"2",<br />
@"channelName":@"My Channel 1",<br />
@"pipMode":@"false" };<br />
<br />
NSDictionary *ad = @{ @"assetid":@"AD12345",<br />
@"title":@"ADTestTitle",<br />
@"type":@"preroll",<br />
@"length":@"20" };<br />
<br />
//static data should be empty in Ad video<br />
NSDictionary *metadata = @{ @"content" : content,<br />
@"ad" : ad,<br />
@"static" : @{} };<br />
<br />
NSDictionary *data = @{ @"metadata" : metadata,<br />
@"event": @"playhead",<br />
@"type": @"ad",<br />
@"playheadPosition": @"0" };<br />
<br />
return data;<br />
}<br />
<br />
@end<br />
<br />
</syntaxhighlight><br />
<br />
==== SDKMethods.h ====<br />
<syntaxhighlight lang="Objective-C"><br />
#import <Foundation/Foundation.h><br />
<br />
@interface SDKMethods : NSObject<br />
<br />
@property(nonatomic, strong) NSURL *url;<br />
<br />
- (NSDictionary *)loadContentData;<br />
- (NSDictionary *)loadPreRollAd;<br />
<br />
@end<br />
</syntaxhighlight><br />
<br />
==== ViewController.m ====<br />
<syntaxhighlight lang="Objective-C"><br />
#import "ViewController.h"<br />
#import "NielsenInit.h"<br />
#import "SDKMethods.h"<br />
#import <MediaPlayer/MediaPlayer.h><br />
#import <AVKit/AVKit.h><br />
#import "Constants.h"<br />
<br />
#import <NielsenAppApi/NielsenEventTracker.h><br />
<br />
NSMutableDictionary *mutableData;<br />
NSDictionary *data;<br />
SDKMethods *sdkMethods;<br />
AVPlayer *player;<br />
AVPlayerViewController *playerController;<br />
NielsenEventTracker *nielsenEventTracker;<br />
<br />
int totalVideosPlayed = 0;<br />
id timeObserver;<br />
<br />
@interface ViewController()<AVPlayerViewControllerDelegate><br />
<br />
@end<br />
<br />
@implementation ViewController<br />
<br />
- (void)viewDidLoad {<br />
[super viewDidLoad];<br />
<br />
//Mark: In NielsenInit class we are initialising the NielsenEventTracker.<br />
<br />
//Getting the instance of NielsenEventTracker<br />
nielsenEventTracker = [NielsenInit createNielsenEventTrackerWithDelegate:nil];<br />
<br />
//Mark: In SDKMethods class we wrote methods which creates content,Ad objects<br />
sdkMethods = [[SDKMethods alloc] init];<br />
<br />
if(self.videoType == onlyContent){<br />
//loading video content data<br />
data = [sdkMethods loadContentData];<br />
}else{<br />
//loading Ad data<br />
data = [sdkMethods loadPreRollAd];<br />
}<br />
<br />
//Converting "data" to mutable dictionary as we have to update playhead, event values.<br />
mutableData =[data mutableCopy];<br />
<br />
[self setPlayer];<br />
[self setPlayHeadPosition];<br />
<br />
//Setting observer to know the completion of video<br />
[self setVideoFinishObserver];<br />
}<br />
<br />
-(void) setPlayer {<br />
<br />
//creating player<br />
player = [AVPlayer playerWithURL:[sdkMethods url]];<br />
playerController = [[AVPlayerViewController alloc] init];<br />
playerController.view.frame = CGRectMake(0,100,self.view.frame.size.width,300);<br />
playerController.player = player;<br />
playerController.showsPlaybackControls = YES;<br />
playerController.delegate = self;<br />
<br />
//Adding observer to player to track play,pause and reverse<br />
[player addObserver:self<br />
forKeyPath:@"rate"<br />
options:(NSKeyValueObservingOptionNew)<br />
context:nil];<br />
<br />
[player play];<br />
<br />
[self addChildViewController:playerController];<br />
[self.view addSubview:playerController.view];<br />
}<br />
<br />
-(void) setPlayHeadPosition {<br />
<br />
//Setting play head position<br />
CMTime timeInterval = CMTimeMakeWithSeconds(1, 1);<br />
[player addPeriodicTimeObserverForInterval:(timeInterval) queue:dispatch_get_main_queue() usingBlock:^(CMTime time){<br />
NSTimeInterval seconds = CMTimeGetSeconds(time);<br />
NSInteger intSec = seconds;<br />
NSString* strSec = [NSString stringWithFormat:@"%li", intSec];<br />
<br />
//updating playHead position in dictionary.<br />
[mutableData setValue:strSec forKey:@"playheadPosition"];<br />
<br />
//Sending data dictionary to SDK with updated playHead position.<br />
[nielsenEventTracker trackEvent:mutableData];<br />
}];<br />
}<br />
<br />
- (void) setVideoFinishObserver {<br />
<br />
//observer fires on completion of Ad<br />
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(itemDidFinishPlaying:) name:AVPlayerItemDidPlayToEndTimeNotification object:playerController.player.currentItem];<br />
}<br />
<br />
//rate 0.0 = Video Pause or stopped<br />
//rate 1.0 = Video played or resumed<br />
//rate -1.0 = Play reversed/rewind.<br />
- (void)observeValueForKeyPath:(NSString *)keyPath<br />
ofObject:(id)object<br />
change:(NSDictionary *)change<br />
context:(void *)context<br />
{<br />
if (object == player && [keyPath isEqualToString:@"rate"]) {<br />
NSNumber * newValue = [change objectForKey:NSKeyValueChangeNewKey];<br />
int intValue = newValue.intValue;<br />
if(intValue == 0){<br />
NSLog(@"playback paused");<br />
<br />
//on video pause, updating event as pause in dictionary<br />
[mutableData setValue:@"pause" forKey:@"event"];<br />
<br />
//sending the dictionary to SDK with "pause" event.<br />
[nielsenEventTracker trackEvent:mutableData];<br />
<br />
}else if(intValue == 1){<br />
NSLog(@"Normal playback");<br />
<br />
//On Play resume setting event as Playhead<br />
[mutableData setValue:@"playhead" forKey:@"event"];<br />
<br />
}<br />
}<br />
}<br />
<br />
- (void)viewDidDisappear:(BOOL)animated<br />
{<br />
//on moving to other screen, updating event as pause in dictionary<br />
[mutableData setValue:@"pause" forKey:@"event"];<br />
<br />
//As it is a pause event setting the playheadPosition to empty.<br />
[mutableData setValue:@"" forKey:@"playheadPosition"];<br />
<br />
player.rate = 0;<br />
[player pause];<br />
<br />
[super viewDidDisappear:animated];<br />
}<br />
<br />
<br />
-(void)itemDidFinishPlaying:(NSNotification *) notification {<br />
<br />
[player removeObserver:self forKeyPath:@"rate"];<br />
<br />
[self sendCompleteEventToSDK];<br />
<br />
//As 1 video completed playing, incrementing the variable value.<br />
totalVideosPlayed += 1;<br />
<br />
//Checking if total videos played or not.<br />
if(totalVideosPlayed != self.totalVideos){<br />
<br />
//Checking if videoType is contentWithAd, then after completion of Ad, will play the content video.<br />
if(self.videoType == contentWithAd){<br />
<br />
//loading video content data<br />
data = [sdkMethods loadContentData];<br />
<br />
mutableData =[data mutableCopy];<br />
<br />
[self setPlayer];<br />
<br />
//Adding observer to player to check is buffering finished<br />
CMTime timeInterval = CMTimeMakeWithSeconds(1, 3);<br />
timeObserver = [player addPeriodicTimeObserverForInterval:(timeInterval) queue:dispatch_get_main_queue() usingBlock:^(CMTime time){<br />
<br />
//checking the video player status<br />
[self handlePlayerStatus:time];<br />
[self setPlayHeadPosition];<br />
//Setting observer to know the completion of video<br />
[self setVideoFinishObserver];<br />
<br />
}];<br />
<br />
}<br />
}<br />
}<br />
<br />
- (void) handlePlayerStatus : (CMTime) time {<br />
<br />
if(player.status == AVPlayerItemStatusReadyToPlay){<br />
<br />
// buffering is finished, setting event as Playhead<br />
[mutableData setValue:@"playhead" forKey:@"event"];<br />
[player removeTimeObserver:timeObserver];<br />
}<br />
}<br />
<br />
- (void) sendCompleteEventToSDK {<br />
<br />
//onCompletion of video, updating event as complete in dictionary<br />
[mutableData setValue:@"complete" forKey:@"event"];<br />
<br />
//As it is a complete event setting the playheadPosition to empty.<br />
[mutableData setValue:@"" forKey:@"playheadPosition"];<br />
<br />
//sending the dictionary to SDK with "complete" event.<br />
[nielsenEventTracker trackEvent:mutableData];<br />
}<br />
<br />
- (void)dealloc {<br />
NSLog(@"Remove NotificationCenter dealloc");<br />
[[NSNotificationCenter defaultCenter] removeObserver:self];<br />
}<br />
@end<br />
</syntaxhighlight><br />
<br />
==== ViewController.h ====<br />
<syntaxhighlight lang="Objective-C"><br />
#import <UIKit/UIKit.h><br />
<br />
@class NielsenEventTracker;<br />
@protocol NielsenEventTrackerDelegate;<br />
<br />
<br />
@interface ViewController : UIViewController<br />
<br />
@property (nonatomic) int videoType;<br />
@property (nonatomic) int totalVideos;<br />
<br />
@end<br />
</syntaxhighlight><br />
<br />
==== OptOutVC.m ====<br />
<syntaxhighlight lang="Objective-C"><br />
#import "OptOutVC.h"<br />
#import "NielsenInit.h"<br />
<br />
<br />
#import <NielsenAppApi/NielsenEventTracker.h><br />
<br />
@interface OptOutVC ()<br />
<br />
@property (weak, nonatomic) IBOutlet UIWebView *webView;<br />
<br />
@end<br />
<br />
@implementation OptOutVC<br />
<br />
- (void)viewDidLoad {<br />
[super viewDidLoad];<br />
<br />
self.nielsenEventTracker = [NielsenInit createNielsenEventTrackerWithDelegate:nil];<br />
<br />
//Getting the optPut URL from eventTracker<br />
[self.webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:self.nielsenEventTracker.optOutURL]]];<br />
}<br />
<br />
@end<br />
</syntaxhighlight><br />
<br />
==== OptOutVC.h ====<br />
<syntaxhighlight lang="Objective-C"><br />
<br />
#import <UIKit/UIKit.h><br />
<br />
@class NielsenEventTracker;<br />
@protocol NielsenEventTrackerDelegate;<br />
<br />
@interface OptOutVC : UIViewController<br />
<br />
@property (nonatomic, weak) NielsenEventTracker *nielsenEventTracker;<br />
<br />
@end<br />
</syntaxhighlight><br />
<br />
}}</div>AlexGutierrezhttps://engineeringportal.nielsen.com//w/index.php?title=Digital_Measurement_Metadata&diff=2999Digital Measurement Metadata2018-10-22T20:02:07Z<p>AlexGutierrez: changed Length description</p>
<hr />
<div>{{Breadcrumb|}} {{Breadcrumb|Digital}} {{Breadcrumb|DCR & DTVR}} {{CurrentBreadcrumb}}<br />
[[Category:Digital]]<br />
<br />
The metadata received for each asset is used for classification and reporting. There are reserved Nielsen keys for collecting the required metadata.<br />
<br />
=== Reserved Keys ===<br />
Content and Ad Metadata can be passed through key-values using the Nielsen reserved keys. The last column in the table below indicates metadata parameters that will be displayed in reporting.<br />
<br />
==== Content Metadata ====<br />
{| class="wikitable"<br />
|-<br />
! Key<br />
! Description<br />
! style="width: 14%;" | Values<br />
! style="width: 12%;" | Required for Audio/Video?<br />
! style="width: 12%;" | Required for Static (page)?<br />
! Reported for<br />
|-<br />
| clientid || Brand value is automatically populated through the App ID provided. The value passed here will override the default value. (e.g. Multiple brands in App) || custom || Optional || Optional || Audio / Video and Static Measurement<br />
|-<br />
| subbrand || VCID value is automatically populated through the App ID provided. This value passed here will override the default value. (e.g. Multiple Sub-brands in App) || custom || Optional || Optional || Audio / Video and Static Measurement<br />
|-<br />
| type ||<br />
Type of measurement:<br />
*audio / video – "content"<br />
*page – "static"<br />
*ad – "preroll", "midroll" or "postroll"<br />
|| "content", "static" || Mandatory || Mandatory || <br />
|-<br />
| assetid || ID assigned to content. Must be unique across all content at the episode level. || custom<br>(no [[Special Characters]]) || Mandatory || Not Required || <br />
|-<br />
| section || Section of site || custom || Not Required || Mandatory || <br />
|-<br />
| isfullepisode || Full episode flag ||<br />
*"y" or "lf" - full episode<br />
*"n" or "sf" - short form<br />
|| Mandatory || Not Required ||<br />
|-<br />
| program || Program name || custom || Mandatory || Not Required || Audio / Video Measurement<br />
|-<br />
| title || Episode title || custom || Mandatory || Not Required || Audio / Video Measurement<br />
|-<br />
| length || Length of content in seconds for VOD. 0 may be used for Live content if the exact content length is not known. || custom || Mandatory || Not Required ||<br />
|-<br />
| segA || Segment A (this is not available for audio / video Measurement reporting as the episode title is reported) || custom || Not Required || Not Required || Static Measurement<br />
|-<br />
| segB || Segment B || custom || Not Required || Not Required || Audio / Video and Static Measurement<br />
|-<br />
| segC || Segment C || custom || Not Required || Not Required || Audio / Video and Static Measurement<br />
|-<br />
| crossId1 || Standard episode ID. Gracenote/TMS ID should be used when available. Must be unique per episode. || custom || Optional || Not Required ||<br />
|-<br />
| crossId2 || Content originator (required only for distributors) || custom || Optional || Not Required ||<br />
|-<br />
| airdate || Original (local) air date and time (Eastern Time for US) || YYYYMMDD HH24:MI:SS || Mandatory || Not Required ||<br />
|-<br />
| pipmode ||<br />
Current state of picture-in-picture (PIP) mode on device.<br />
*"true" if audio / video measurement is displayed in PIP mode<br />
*"false" if audio / video measurement is displayed in regular mode<br />
|| "true", "false" || Optional || Not Required || Audio / Video Measurement<br />
|-<br />
| adloadtype ||<br />
Type of ad load:<br />
#Linear - matches TV ad load<br />
#Dynamic - Dynamic Ad Insertion (DAI)<br />
|| "1" - Linear<br />
"2" - Dynamic<br />
|| Mandatory for DCR || Not Required || DCR <br />
|-<br />
| adModel ||<br />
Type of ad model:<br />
#Linear - matches TV ad load<br />
#Dynamic - Dynamic Ad Insertion (DAI)<br />
|| "1" - Linear<br />
"2" - Dynamic<br />
|| Mandatory for DTVR || Not Required || DTVR<br />
|-<br />
| progen || Genre (required only for non-TV originated content). See [[DCR OTT Genre List]] for acceptable values. || e.g. DD for Daytime Drama || Required for non-TV content || Not Required<br />
|}<br />
<br />
'''clientid & subbrand (vcid)'''<br />
By default, clientid and subbrand are setup in Nielsen backend configuration to capture brand and sub-brand information. The fields get populated from backend for a registered client appid. But if an app contains multiple brands and sub-brands and client is willing to give credit to another brand or sub-brand then :<br />
*Client app developer can override the clientid and subbrand (brand and sub-brand info.) in [[loadMetadata]] call to get a proper measurement for a desired brand and sub-brand.<br />
**If no clientid and subbrand are specified in CMS of a content and an ad, default values reported in the Configuration will be reported.<br />
**If clientid and subbrand are specified in CMS of the content or ad, the most recent clientid and subbrand will be reported in subsequent pings. To report with a different clientid and subbrand, include the new values for the keys in their subsequent [[loadMetadata]] call.<br />
'''Picture-in-picture (PIP) mode'''<br />
Once the app detects PIP mode, call [[loadMetadata]] with the same set of assetids, values and with one additional parameter "pipmode": "true". Once PIP mode is disabled in the device, call [[loadMetadata]] again with "pipmode":"false".<br />
<br />
==== Custom Variables Extension ====<br />
Contact Nielsen Technical Account Manager (TAM) to configure any custom variables, as needed for implementing the application.<br />
<br />
See [[Custom Variables Extension]] for more information.<br />
<br />
==== Advertisement Metadata ====<br />
{| class="wikitable"<br />
|-<br />
! Key !! Description !! Values !! Required for Audio / Video? !! Required for Static (page)<br />
|-<br />
| type || Type of ad || "preroll"<br />
"midroll"<br />
"postroll" <br />
|| Mandatory || Not Required<br />
|-<br />
| assetid || An ID assigned to the advertisement. Can be an internal ID, an ID provided by the ad server, or a randomly generated ID. Must be unique across all ads || custom || Mandatory || Not Required<br />
|-<br />
| title || Title of the advertisement || "MyAdName" || Mandatory || Not Required<br />
|}<br />
<br />
=== Passing Metadata ===<br />
The required metadata can be passed as key values through the loadMetadata method. The sample code below shows a metadata objects for various assets.<br />
'''Audio / Video Measurement Asset'''<br />
Type should be "content"<br />
<syntaxhighlight lang="json"> {<br />
"type": "content",<br />
"assetName": "myassetName",<br />
"length": "300.0",<br />
"title": "myTitle",<br />
"program": "myProgram",<br />
"censuscategory": "myCensusCategory",<br />
"assetid": "myAssetId",<br />
"channelName": "myChannel",<br />
"adloadtype": "2",<br />
"segB": "segmentB",<br />
"segC": "segmentC",<br />
"isfullepisode":"y",<br />
"crossId1": "Reference11",<br />
"crossId2": "Reference22",<br />
"airdate": "20161013 20:00:00"<br />
}</syntaxhighlight><br />
<br />
'''Static (Page) Measurement Asset'''<br />
Type should be "static"<br />
<syntaxhighlight lang="json"> {<br />
"type": "static",<br />
"assetid": "static123",<br />
"assetName": "Page-Asset",<br />
"section": "siteSection",<br />
"segA": "segmentA",<br />
"segB": "segmentB",<br />
"segC": "segmentC"<br />
}</syntaxhighlight><br />
<br />
'''For any of the ad types'''<br />
<syntaxhighlight lang="json"> {<br />
"type": "midroll",<br />
"length": "30.0",<br />
"assetid": "myMidrollAssetId",<br />
"adloadtype": "2",<br />
"tv": "true",<br />
"dataSrc": "cms"<br />
}</syntaxhighlight><br />
<blockquote>'''Note''': In case the individual ad details are not available, send ad pod (presence) details through the <code>loadMetadata</code> and playhead position through <code>setPlayheadPosition</code>.</blockquote><br />
<br />
'''loadMetadata'''<br />
The object can then be passed when calling [[loadMetadata]].<br />
<syntaxhighlight lang="java"> loadMetadata(jsonMetadataObject);</syntaxhighlight></div>AlexGutierrezhttps://engineeringportal.nielsen.com//w/index.php?title=sendID3&diff=2844sendID32018-08-01T15:53:57Z<p>AlexGutierrez: added DASH reference</p>
<hr />
<div>{{Breadcrumb|}} {{Breadcrumb|Digital}} {{Breadcrumb|iOS SDK API Reference}} {{CurrentBreadcrumb}}<br />
[[Category:Digital]]<br />
[[Category:iOS SDK API Reference]]<br />
This API is a receiver for HLS & DASH timed metadata events (ID3 tags) provided through iOS’s NSNotificationCenter notification system. This API filters out Nielsen-specific ID3 tags from the system and buffers the data for transfer to Nielsen’s collection facility.<br />
<br />
== Syntax ==<br />
<syntaxhighlight lang="objective-c"><br />
– (void) sendID3: (NSString *) data<br />
</syntaxhighlight><br />
<br />
== Input Parameters ==<br />
{| class="wikitable"<br />
|-<br />
! Parameter !! Description<br />
|-<br />
| data || An NSString object that contains only the owner ID in the PRIV frame of an ID3 tag.<br />
|}<br />
<br />
== Output Parameters ==<br />
{| class="wikitable"<br />
|-<br />
! Output Parameters (Return value) !! Description<br />
|-<br />
| Void || <br />
|}<br />
<br />
== Notes ==<br />
* During the HLS timed metadata event, only the PRIV frame’s owner ID of the payload (or a copy of the payload) from the NSNotificationCenter notification system shall be passed into this API. This API ignores any owner ID that does not pertain to Nielsen. Since ID3 tags are continuously streamed, every timed metadata event must be captured, stored, and transferred for accuracy of measurement.<br />
* It is not necessary for the player application to Enable / Disable the Nielsen App SDK component depending on whether Nielsen ID3 tags are present in the stream. This is handled automatically by the SDK</div>AlexGutierrezhttps://engineeringportal.nielsen.com//w/index.php?title=AppSDK()&diff=2779AppSDK()2018-05-23T17:02:08Z<p>AlexGutierrez: </p>
<hr />
<div>{{Breadcrumb|}} {{Breadcrumb|Digital}} {{Breadcrumb|Android SDK API Reference}} {{CurrentBreadcrumb}}<br />
[[Category:Digital]]<br />
[[Category:Android SDK API Reference]]<br />
<br />
<code>AppSdk()</code> is the public constructor used for initializing the App SDK Analytics framework.<br />
*The reference to the App SDK object returned by the Appsdk constructor.<br />
*App SDK supports multiple instances (max. four) to measure multiple players simultaneously.<br />
*context, appInfo parameters are mandatory and should not be NULL values.<br />
*The constructor should never return null.<br />
<br />
=== Debug flag for development environment ===<br />
Player application developers / integrators can use Debug flag to check whether an App SDK API call made is successful.<br />
To activate the Debug flag,<br />
*Pass the argument <code>+ ""nol_devDebug" : "" + "I" + "",",</code> while [[initializing the App SDK object]]. The permitted values are<br />
**'I' (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.<br />
**'W' (WARNING): Indicates potential integration / configuration errors or SDK issues.<br />
**'E' (ERROR): Indicates important integration errors or non-recoverable SDK issues.<br />
**'D' (DEBUG): Debug logs, used by the developers to debug more complex issues.<br />
Once the flag is active, it logs each API call made and the data passed. The log created by this flag is minimal.<br />
<blockquote>'''Note:''' DO NOT activate the Debug flag in production evironment.</blockquote><br />
<br />
== Syntax ==<br />
<syntaxhighlight lang="java"><br />
public AppSdk(Context context, JSONObject appInfo, IAppNotifier notifier);<br />
</syntaxhighlight><br />
<br />
== Input Parameters ==<br />
{| class="wikitable"<br />
|-<br />
! Parameter !! Description<br />
|-<br />
| context || The context from the App<br />
|-<br />
| appInfo || JSON object that defines the application information.<br />
|-<br />
| notifier || Client-provided object implementing the iAppNotifier interface. Can be ‘null’ if not provided by client.<br />
|}<br />
<br />
== Output Parameters ==<br />
{| class="wikitable"<br />
|-<br />
! Output Parameters (Return value) !! Description<br />
|-<br />
| A reference to the instance. Else Null object || <br />
|}<br />
<br />
== Notes ==<br />
=== JSON object format for appInfo ===<br />
<syntaxhighlight lang="java"><br />
JSONObject appSdkConfig = new JSONObject()<br />
.put("appid", "PXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX")<br />
.put("appname", "Sample App Name")<br />
.put("sfcode", "uat-cert")<br />
.put("custom_key1", "custom_value1")<br />
.put("custom_key2", "custom_value2");<br />
</syntaxhighlight><br />
<br />
=== JSON string format for appInfo ===<br />
The required items are application name (appName), application ID (appid), designated market area (dma), Nielsen-assigned data node (sfcode), and the country code (ccode). <br />
<blockquote>'''Note:''' Notice that all quotes inside the string are escaped with a backslash (\).</blockquote><br />
<syntaxhighlight lang="java"><br />
"{"appName":"PlayerApp","appid":"PXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX,"sfcode":"us"}"<br />
</syntaxhighlight><br />
<br />
=== iAppNotifier ===<br />
Client apps can subscribe to the App SDK event listener to be notified about App SDK events. To implement this feature, create a plain old java object (POJO) that implements the interface <code>com.nielsen.app.sdk.IAppNotifier</code>. Do not implement with the main activity class as this creates a strong reference and interferes with memory release.<br />
<br />
<syntaxhighlight lang="java"><br />
//pass the implementing object to the AppSdk constructor in order to subscribe to the eventlistener:<br />
mAppSdk = new AppSdk(context, appInfo,iappNotifierObject)<br />
</syntaxhighlight><br />
<br />
Optionally, implement an object derived from the interface, as below. This object will allow listening for events within the App SDK.<br />
<syntaxhighlight lang="java"><br />
public interface iAppNotifier<br />
{<br />
public void onAppSdkEvent(long timestamp, int code, String description);<br />
}<br />
</syntaxhighlight><br />
<br />
Note that implementing the event listener is '''optional'''.<br />
<br />
==== AppSdk events ====<br />
There are three events currently exposed on the App SDK.<br />
<syntaxhighlight lang="java"><br />
public static final int EVENT_INITIATE = 2000;<br />
</syntaxhighlight><br />
*App SDK notifies that it is going to be initialized.<br />
<br />
<syntaxhighlight lang="java"><br />
public static final int EVENT_STARTUP = 2001;<br />
</syntaxhighlight><br />
*The App SDK has just started up. It will happen only after the App SDK has received a valid config file from Nielsen. This is the location in the code to acquire value of <code>userOptOutURLString()</code>.<br />
<br />
<syntaxhighlight lang="java"><br />
public static final int EVENT_SHUTDOWN = 2002;<br />
</syntaxhighlight><br />
*The App SDK is shutting down. It will happen only when the App SDK is destroyed.</div>AlexGutierrezhttps://engineeringportal.nielsen.com//w/index.php?title=DTVR_Android_SDK&diff=2705DTVR Android SDK2018-05-04T21:49:50Z<p>AlexGutierrez: removed mention of playheads in DTVR</p>
<hr />
<div>{{Breadcrumb|}} {{Breadcrumb|Digital}} {{Breadcrumb|DCR & DTVR}} {{CurrentBreadcrumb}}<br />
[[Category:Digital]]<br />
<br />
== Prerequisites ==<br />
To start using the App SDK, the following items are required:<br />
{| class="wikitable"<br />
|-<br />
! style="width: 30px;" |<br />
! style="width: 15%;" | Item<br />
! Description<br />
! Source<br />
|-<br />
|| ☑ || '''App ID (appid)''' || Unique ID assigned to the player/site and configured by product. || Contact Nielsen<br />
|-<br />
|| ☑ || '''sfcode''' || Environment that the SDK must point to || Contact Nielsen<br />
|-<br />
|| ☑ || '''Nielsen SDK''' || Includes SDK libraries and '''sample implementation'''; ''See [[Android SDK Release Notes]]'' || [[Special:Downloads|Download]]<br />
|}<br />
If you do not have any of these pre-requisites or if you have any questions, please contact our SDK sales support team.<br />
Refer to [[Digital Measurement Onboarding]] for more information on how to get a Nielsen App SDK and appid.<br />
<br />
== Import Library ==<br />
Refer to [[Android SDK API Reference#Setting Up Development Environment|Android SDK API Reference - Setting Up Development Environment]] for information on importing libraries.<br />
* The latest version of App SDK allows instantiating multiple instances of App SDK object and can be used simultaneously without any issues.<br />
<blockquote>Note: The latest version of App SDK contains only appsdk.jar file and does not feature any native shared libraries like libAppSdk.so.</blockquote><br />
<br />
== Initialize SDK ==<br />
Initialize App SDK as soon as the application is launched. Refer to [[Android SDK API Reference#Initialization|Android SDK API Reference - Initialization]] for details on initializing an AppSDK object and the parameters required.<br />
<br />
== Configure and fire API calls ==<br />
=== Configure API calls - play ===<br />
Call [[play()]] when starting or resuming a streaming session to pass the channel descriptor information through <code>channelName</code> parameter when the user taps the '''Play''' button on the player.<br />
* <code>channelName</code> is a 32-character free-form text field containing the name of the program or feed being sent (such as ESPN2, Food Network, etc.)<br />
Call [[play()]] with <code>channelName</code> JSON as below.<br />
<syntaxhighlight lang="json"> {<br />
"channelName": "TheMovieTitle"<br />
}</syntaxhighlight><br />
<br />
=== Configure API calls - loadMetadata ===<br />
Load the CMS metadata by calling [[loadMetadata|loadMetadata()]] on the SDK object.<br />
<syntaxhighlight lang="swift"> loadMetadata(JSONObject jsonMetadata);</syntaxhighlight><br />
Refer to [[Digital Measurement Metadata]] for the list of parameters to be passed in the JSON object.<br />
Call [[loadMetadata()]] for the content after the first [[play()]] call. Call [[loadMetadata()]] with JSON metadata for the active content as below.<br />
<syntaxhighlight lang="json"> {<br />
"type": "content",<br />
"adModel": "1"<br />
}</syntaxhighlight><br />
<br />
=== Configure API calls - sendID3 ===<br />
[[sendID3()]] API is a receiver for HLS timed metadata events (ID3 tags) provided through the notification system. This API filters out Nielsen-specific ID3 tags from the system and buffers the data for transfer to Nielsen’s collection facility. Call [[sendID3()]] whenever new Nielsen ID3 metadata is available for processing during session playback.<br />
==== Sample ID3 tags ====<br />
* <code>www.nielsen.com/X100zdCIGeIlgZnkYj6UvQ==/X100zdCIGeIlgZnkYj6UvQ==/AAAB2Jz2_k74GXSzx4npHuI_<wbr />JwJd3QSUpW30rDkGTcbHEzIMWleCzM-uvNOP9fzJcQMWQLJqzXMCAxParOb5sGijSV9dNM3QiBniJYGZ5GI-lL1fXTTN0IgZ4iWBmeRiPpS9AAAAAAAAAAAAAAAAAAAAAFJWFM5SVhTONNU=/00000/00000/00</code><br />
* <code>www.nielsen.com/X100zdCIGeIlgZnkYj6UvQ==/R8WHe7pEBeqBhu8jTeXydg==/AAICoyitYqlxT7n6aZ0oMCGhe<wbr />Fi4CXFp46AMUPZz1lMr_M9tr3_cjee1SHqxrOiVerMDLeyn9xzocZSKwi746Re8vNOtpNCAZjYABs_J0R25IHpvOc1HS8<wbr />QHGgD5TgOJeS6gX100zdCIGeIlgZnkYj6UvVJWFNhSVhTiPE0=/00000/46016/00</code><br />
<blockquote>'''Note''': ID3 tags are not applicable for International (Germany)</blockquote><br />
<br />
Refer to [[Android SDK API Reference#Retrieving ID3 Tags|Android SDK API Reference - Retrieving ID3 Tags]] section to know more details.<br />
<br />
===Configure API calls - stop ===<br />
Call [[stop()]] only when the stream playback is stopped by a user. Common events are; network loss, power / standby, incoming call, alarm, etc. Call [[stop()]] only when buffering continues for 30 seconds (not when it starts). Call [[play()]] when resuming a stream (previously stopped) or starting playback of a new stream.<br />
<br />
===Configure API calls - end ===<br />
Call [[end()]] only at the end of playback. Not mandatory if stream is live/does not end.<br />
<br />
==API Call sequence==<br />
===Use Case 1: Content has no Advertisements===<br />
Call [[play()]] with <code>channelName</code> JSON as below:<br />
<syntaxhighlight lang="json"> {<br />
"channelName": "TheMovieTitle"<br />
}</syntaxhighlight><br />
<br />
===Use Case 2: Content has linear (non-DAI) Advertisements===<br />
Call [[play()]] (for the first ad) with <code>channelName</code> JSON as below:<br />
<syntaxhighlight lang="json"> {<br />
"channelName": "TheMovieTitle"<br />
}</syntaxhighlight><br />
Call [[loadMetadata()]] with JSON metadata for the active content as below:<br />
<syntaxhighlight lang="json"> {<br />
"adModel": "1"<br />
}</syntaxhighlight><br />
Call [[sendID3()]] with ID3 payload<br />
<syntaxhighlight lang="java"><br />
sendID3(payload)<br />
</syntaxhighlight><br />
<br />
== Handling Foreground and Background states of App==<br />
<br />
There are two ways of handling the foreground and background states of the client application.<br />
*Let App SDK handle the app states information (foreground / background) and use it, as necessary.<br />
*Capture app states through the application and trigger the corresponding API ([[appInForeground()]] or [[appInBackground()]]) upon every change of state. This allows Nielsen App SDK to know the app state.<br />
<br />
=== New devices (Android 4.0 and later versions) ===<br />
<br />
'''Add ''application'' tag to Manifest XML file'''<br />
When a client’s app supports only new devices (Android 4.0 and above) and the client has not implemented a custom Application class for some other purpose,<br />
* Add the following entry (application tag) into the Manifest XML file to use SDK’s state detection feature.<br />
<syntaxhighlight lang="xml"> <application android:name="com.nielsen.app.sdk.AppSdkApplication"></syntaxhighlight><br />
This is the custom Application class where the whole background detection implementation is done. No new permissions are required to change the properties in Manifest XML file.<br />
<br />
=== Older devices (Android 4.0 or earlier version) ===<br />
<br />
Identify the change of state through the application and call the respective API ([[appInForeground()]] or [[appInBackground()]]) upon every change of state (foreground / background). The SDK will use this information to pass the app launch times, etc. to Collection facility.<br />
<syntaxhighlight lang="java"><br />
AppLaunchMeasurementManager.appInForeground(getApplicationContext());<br />
AppLaunchMeasurementManager.appInBackground(getApplicationContext());<br />
</syntaxhighlight><br />
<br />
== Interruptions during playback ==<br />
As part of integrating Nielsen App SDK with the player application, the Audio / Video app developer needs to handle the following possible interruption scenarios:<br />
* Pause / Play<br />
* Network Loss (Wi-Fi / Airplane / Cellular)<br />
* Call Interrupt (SIM or Third party Skype / Hangout call)<br />
* Alarm Interrupt<br />
* Content Buffering<br />
* Device Lock / Unlock (Video players only, not for Audio players)<br />
* App going in the Background/Foreground (Video players only, not for Audio players)<br />
* Channel / Station Change Scenario<br />
* Unplugging of headphone<br />
In case of encountering one of the above interruptions, the player application needs to<br />
* Call [[stop()]] immediately (except when content is buffering) and withhold sending ID3 events.<br />
* Start sending pings – [[loadMetadata()]] and [[sendID3()]] for the new viewing session, once the playback resumes.<br />
Please see the [[Digital Measurement FAQ]] for more details<br />
<br />
== Nielsen Measurement Opt-Out Implementation ==<br />
As a global information and measurement leader, we are committed to protecting the privacy and security of the data we collect, process and use. Our digital measurement products are not used to identify the consumer in any way, but they help us and our clients measure and analyze how consumers engage with media across online, mobile and emerging technologies, and offer insights into consumer behavior.<br />
* When the app user wants to opt in or opt out of Nielsen measurement, a new dynamic page (with content string obtained from [[userOptOutURLString()]]) should be displayed.<br />
** Use [[getOptOutStatus()]] to retrieve the device’s Opt-Out status.<br />
* This Opt-out page should be displayed in a webview (within the app) and not in any external browser.<br />
* Capture the user’s selection in this page and pass it to the SDK through [[userOptOut()]] for Nielsen to save the user’s preference.<br />
* For more details, refer to [[Android SDK API Reference#Android Opt-Out Implementation|Android SDK API Reference - Android Opt-Out Implementation]] and Nielsen Digital Privacy.<br />
<br />
== Testing an Implementation - App ==<br />
See [[Digital Measurement Testing]].</div>AlexGutierrezhttps://engineeringportal.nielsen.com//w/index.php?title=DTVR_iOS_SDK&diff=2704DTVR iOS SDK2018-05-04T21:48:54Z<p>AlexGutierrez: fixed mention of playhead position for DTVR</p>
<hr />
<div>{{Breadcrumb|}} {{Breadcrumb|Digital}} {{Breadcrumb|DCR & DTVR}} {{CurrentBreadcrumb}}<br />
[[Category:Digital]]<br />
<br />
== Prerequisites ==<br />
To start using the App SDK, the following items are required:<br />
{| class="wikitable"<br />
|-<br />
! style="width: 30px;" |<br />
! style="width: 15%;" | Item<br />
! Description<br />
! Source<br />
|-<br />
|| ☑ || '''App ID (appid)''' || Unique ID assigned to the player/site and configured by product. || Contact Nielsen<br />
|-<br />
|| ☑ || '''sfcode''' || Environment that the SDK must point to || Contact Nielsen<br />
|-<br />
|| ☑ || '''Nielsen SDK''' || Includes SDK libraries and '''sample implementation'''; ''See [[iOS SDK Release Notes]]'' || [[Special:Downloads|Download]]<br />
|}<br />
If you do not have any of these pre-requisites or if you have any questions, please contact our SDK sales support team.<br />
Refer to [[Digital Measurement Onboarding]] for more information on how to get a Nielsen App SDK and appid.<br />
<br />
== Import Library ==<br />
Refer to [[iOS SDK API Reference#Importing Frameworks|Importing Frameworks]] for information on importing libraries.<br />
* The latest version of App SDK allows instantiating multiple instances of App SDK object and can be used simultaneously without any issues.<br />
<br />
== Initialize SDK ==<br />
Initialize App SDK as soon as the application is launched. Refer to [[iOS SDK API Reference#Initialization|Initialization]] for details on initializing an AppSDK object and the parameters required.<br />
<br />
== Configure and fire API calls ==<br />
=== Configure API calls - play ===<br />
Use [[play]] to pass the channel descriptor information through channelName parameter when the user taps the '''Play''' button on the player.<br />
* <code>channelName</code> is a 32-character free-form text field containing the name of the program or feed being sent (such as ESPN2, Food Network, etc.)<br />
Call [[play]] with <code>channelName</code> JSON as below.<br />
<syntaxhighlight lang="json"> {<br />
"channelName": "TheMovieTitle"<br />
}</syntaxhighlight><br />
<br />
=== Configure API calls - loadMetadata ===<br />
Use [[loadMetadata]] to pass ‘content’ and ‘ad’ [[Digital Measurement Metadata]]. The CMS data must be passed as a JSON object.<br />
<syntaxhighlight lang="swift"> – (void) loadMetadata :(id)metadata;</syntaxhighlight><br />
Refer to [[loadMetadata]] for the list of parameters to be passed in the JSON object.<br />
Call [[loadMetadata]] after the first play call. Call [[loadMetadata]] with JSON metadata for the active content as below.<br />
<syntaxhighlight lang="json"> {<br />
"type": "content",<br />
"adModel": "1"<br />
}</syntaxhighlight><br />
<br />
=== Configure API calls - sendID3 ===<br />
[[sendID3]] API is a receiver for timed metadata events (ID3 tags) provided through iOS’s NSNotificationCenter notification system. This API filters out Nielsen-specific ID3 tags from the system and buffers the data for transfer to Nielsen’s collection facility.<br />
==== Sample ID3 tags ====<br />
* <code>www.nielsen.com/X100zdCIGeIlgZnkYj6UvQ==/X100zdCIGeIlgZnkYj6UvQ==/AAAB2Jz2_k74GXSzx4npHuI_<wbr />JwJd3QSUpW30rDkGTcbHEzIMWleCzM-uvNOP9fzJcQMWQLJqzXMCAxParOb5sGijSV9dNM3QiBniJYGZ5GI-lL1fXTTN0IgZ4iWBmeRiPpS9AAAAAAAAAAAAAAAAAAAAAFJWFM5SVhTONNU=/00000/00000/00</code><br />
* <code>www.nielsen.com/X100zdCIGeIlgZnkYj6UvQ==/R8WHe7pEBeqBhu8jTeXydg==/AAICoyitYqlxT7n6aZ0oMCGhe<wbr />Fi4CXFp46AMUPZz1lMr_M9tr3_cjee1SHqxrOiVerMDLeyn9xzocZSKwi746Re8vNOtpNCAZjYABs_J0R25IHpvOc1HS8<wbr />QHGgD5TgOJeS6gX100zdCIGeIlgZnkYj6UvVJWFNhSVhTiPE0=/00000/46016/00</code><br />
Refer to [[iOS SDK API Reference#Retrieving ID3 Tags|Retrieving ID3 Tags]] section to know more details.<br />
<br />
===Configure API calls - stop ===<br />
Call [[stop]] in case of interruptions during playback like flight mode, Wi-Fi toggle, etc. Call [[play]] when resuming the stream / starting the new stream.<br />
<br />
===Configure API calls - end ===<br />
Call [[end]] only at the end of playback.<br />
<br />
== Interruptions during playback ==<br />
As part of integrating Nielsen App SDK with the player application, the Audio / Video app developer needs to handle the following possible interruption scenarios:<br />
* Pause / Play<br />
* Network Loss (Wi-Fi / Airplane / Cellular)<br />
* Call Interrupt (SIM or Third party Skype / Hangout call)<br />
* Alarm Interrupt<br />
* Content Buffering<br />
* Device Lock / Unlock (Video players only, not for Audio players)<br />
* App going in the Background/Foreground (Video players only, not for Audio players)<br />
* Channel / Station Change Scenario<br />
* Unplugging of headphone<br />
In case of encountering one of the above interruptions, the player application needs to<br />
* Call [[stop]] immediately (except when content is buffering) and withhold sending ID3 events.<br />
* Start sending pings – [[loadMetadata]] and [[sendID3]] for the new viewing session, once the playback resumes.<br />
Please see the [[Digital Measurement FAQ]] for more details<br />
<br />
== Nielsen Measurement Opt-Out Implementation ==<br />
As a global information and measurement leader, we are committed to protecting the privacy and security of the data we collect, process and use. Our digital measurement products are not used to identify the consumer in any way, but they help us and our clients measure and analyze how consumers engage with media across online, mobile and emerging technologies, and offer insights into consumer behavior.<br />
* When the app user wants to opt in or opt out of Nielsen measurement, a new dynamic page (with content string obtained from [[optOutURL]]) should be displayed.<br />
* Use [[optOutStatus]] to retrieve the device’s Opt-Out status.<br />
* This Opt-out page should be displayed in a webview (within the app) and not in any external browser.<br />
* Capture the user’s selection in this page and pass it to the SDK through [[userOptOut]] for Nielsen to save the user’s preference.<br />
<!-- * For more details, refer to [[iOS SDK API Reference#iOS Opt-Out Implementation|iOS SDK API Reference - iOS Opt-Out Implementation]] and Nielsen Digital Privacy. --><br />
<br />
== Testing an Implementation - App ==<br />
See [[Digital Measurement Testing]].</div>AlexGutierrezhttps://engineeringportal.nielsen.com//w/index.php?title=DCR_Video_%26_Static_Cloud_API&diff=2653DCR Video & Static Cloud API2018-04-16T21:34:17Z<p>AlexGutierrez: </p>
<hr />
<div>{{Breadcrumb|}} {{Breadcrumb|Digital}} {{Breadcrumb|DCR & DTVR}} {{CurrentBreadcrumb}}<br />
[[Category:Digital]]<br />
<br />
This guide shows you how to integrate the Nielsen Cloud API to enable Digital Content Ratings (DCR) measurement on your over-the-top (OTT) Apps.<br />
*For Roku Apps, please see [[DCR Video & Static Roku Cloud API]]<br />
*For Mobile Apps, please see [[DCR Video & Static Mobile Cloud API]]<br />
<br />
==Prerequisites==<br />
To get started, you will need a Nielsen App ID. The App ID is a unique ID assigned to your app. This will be provided to you upon starting the integration.<br />
<br />
<syntaxhighlight lang="javascript">XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX</syntaxhighlight><br />
<br />
==Integration==<br />
We will cover the steps for constructing the Cloud API Calls.<br />
<br />
===Request Overview===<br />
<br />
====URL Structure====<br />
<br />
The Cloud API Calls are HTTP GET Requests with the URL structure:<br />
<br />
<syntaxhighlight lang="javascript">[endpoint]/[appid]/[sessionID]/a?b=[payload]</syntaxhighlight><br />
<br />
The URL includes the following components:<br />
<br />
*<code>[endpoint]</code>: location of data collection environment<br />
*<code>[appid]</code>: provided App ID<br />
*<code>[sessionID]</code>: unique value for each user session<br />
*<code>[payload]</code>: metadata and events<br />
<br />
====Endpoint====<br />
<br />
There are endpoints for testing and production:<br />
<br />
*Testing: <code>https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/</code><br />
*Production: <code>https://cloudapi.imrworldwide.com/nmapi/v2/</code><br />
<br />
During testing, all calls should be pointed to the testing endpoint. We will review the update to the production endpoint during the Go Live section of this guide.<br />
<br />
====URL Example====<br />
As you move through the integration steps, you can reference the below URL structure with the expanded payload:<br />
<br />
<syntaxhighlight lang="javascript"><br />
https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=<br />
{<br />
"devInfo": [deviceInfo],<br />
"metadata": {<br />
"static": [static_metadata],<br />
"content": [content_metadata],<br />
"ad": [ad metadata]<br />
},<br />
"event": [event],<br />
"position": [playhead_position],<br />
"type": [asset type],<br />
"utc": [UTC]<br />
}<br />
</syntaxhighlight><br />
<br />
===Create Session ID===<br />
A unique Session ID must be created upon app launch and provided in the URL. This will allow measurement to occur for the entire duration that a user is within the app.<br />
<br />
The session ID must be a GUID that is passed with every request and remain consistent throughout each individual session. You can use a GUID or a unique random number as shown in the recommended sample code below:<br />
<br />
<syntaxhighlight lang="javascript">sessionID = Date.now()+String(Math.random()*1000000 >> 0);</syntaxhighlight><br />
<br />
Upon exiting the app, the session will need to be terminated using the delete event. Sessions will automatically expire after 30 minutes of cloud inactivity.<br />
<br />
===Define URL Structure===<br />
Define the URL structure using your provided <code>[appid]</code> and a unique <code>[sessionID]</code>.<br />
<br />
<syntaxhighlight lang="javascript">https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=[payload]</syntaxhighlight><br />
<br />
===Configure Payload===<br />
<br />
All Cloud API requests must contain the following payload data:<br />
<br />
*''devInfo'': device and app info<br />
*''metadata'': asset metadata<br />
*''event metadata'': type of event<br />
<br />
The payload can be passed through key-values using the Nielsen reserved keys. The specific keys and descriptions are highlighted in the tables included in this section.<br />
<br />
'''Payload Example'''<br />
<br />
The example below should be referenced when following the steps for configuring the request payload.<br />
<br />
<syntaxhighlight lang="javascript"><br />
// 3.1 Configure Payload: devInfo <br />
payload = {<br />
"devInfo": {<br />
"devId": "AD-ID", <br />
"apn": "AppName",<br />
"apv": "1.0",<br />
"uoo": "false"<br />
},<br />
<br />
// 3.2 Configure Payload: metadata<br />
"metadata": {<br />
"static": {}, // object for measuring static content<br />
"content": { // object for measuring video content<br />
"type": "content", // "content" for video<br />
"assetid": "VIDEO-ID123", // unique ID for video<br />
"isfullepisode": "y", // full episode flag<br />
"program": "Program Name", // program name<br />
"title": "Episode Title S3 - EP1", // episode name<br />
"length": "1800", // content duration in seconds<br />
"segB": "Custom Segment B", // custom segment<br />
"segC": "Custom Segment C", // custom segment<br />
"crossId1": "Standard Episode ID", // episode ID<br />
"crossId2": "Content Originator ID", // content orginator (required for distributors)<br />
"airdate": "20161013 20:00:00", // airdate<br />
"adloadtype": "2" //ad load flag<br />
"hasAds": "1", // content contains ads = 1 / no ads = 0<br />
"progen": "CV" // program genre abbreviation<br />
},<br />
"ad": {<br />
"type": "preroll", // type of ad<br />
"assetid": "AD-ID123" // unique ID for ad<br />
}<br />
},<br />
<br />
// 3.3 Configure Payload: events<br />
"event": "playhead", //event name<br />
"position": "300", // position in seconds<br />
"type": "content", //"content" or "ad"<br />
"utc": "1456448742000" //unix timestamp in milliseconds <br />
}<br />
</syntaxhighlight><br />
<br />
=====Configure Payload: devInfo=====<br />
An object <code>"devInfo"</code> will need to be created to capture App and Device information.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| devId || unique ID to identify user (e.g. Advertising ID, Roku Device ID) || custom || Yes<br />
|-<br />
| apn || app name || custom || Yes<br />
|-<br />
| apv || app build version || custom || Yes<br />
|-<br />
| uoo || device opt-out status || <code>"true"</code> or <code>"false"</code> || Yes<br />
|-<br />
|}<br />
<br />
'''Example devInfo Object'''<br />
<syntaxhighlight lang="javascript"><br />
// create devInfo object<br />
"devInfo": {<br />
"devId": "AD-ID",<br />
"apn": "AppName",<br />
"apv": "1.0",<br />
"uoo": "false"<br />
},<br />
</syntaxhighlight><br />
<br />
==== 3.2 Configure Payload: metadata ====<br />
Asset metadata can be passed through <code>"metadata"</code>. There are two asset types: <code>"content"</code> for video and <code>"ad"</code> for ads. The metadata received for each asset is used for classification and reporting.<br />
<br />
You will need to set up <code>"metadata"</code> objects for <code>"content"</code> and <code>"ad"</code> with the required Nielsen keys as shown in the sample code below.<br />
<br />
===== Content Metadata =====<br />
Content metadata should remain constant throughout the entirety of an episode/clip including when ads play.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| type || type of asset || <code>"content"</code> || Yes<br />
|-<br />
| assetid || unique ID assigned to asset || custom || Yes<br />
|-<br />
| program ||name of program (25 character limit) || custom || Yes<br />
|-<br />
| title ||name of program (25 character limit) || custom || Yes<br />
|-<br />
| length || length of content in seconds || <code>seconds</code> (86400 for live stream) || Yes<br />
|-<br />
| segB || custom segment B || custom || <br />
|-<br />
| segC || custom segment C || custom || <br />
|-<br />
| airdate || the airdate in the linear TV || YYYYMMDD HH24:MI:SS || Yes<br />
|-<br />
| isfullepisode || full episode flag || <code>"y"</code>- full episode, <code>"n"</code>- non full episode || Yes<br />
|-<br />
| crossId1 || standard episode ID || custom || Yes<br />
|-<br />
| crossId2 || content originator (only required for distributors) || Nielsen ||<br />
|-<br />
| adloadtype || type of ad load:<br />
<code>"1"</code> Linear – matches TV ad load<br />
<br />
<code>"2"</code> Dynamic – Dynamic Ad Insertion (DAI)<br />
|| <code>"2"</code> - DCR measures content with dynamic ads || Yes<br />
|-<br />
| hasAds || ads indicator<br />
<code>"1"</code>: ads included<br />
<br />
<code>"0"</code>: ads not included<br />
|| <code>"1"</code> or <code>"0"</code> || Yes<br />
|-<br />
| progen || Genre (required only for non-TV originated content). See [[DCR OTT Genre List]] for accepted values || <code>"CV"</code> for Comedy Variety || Required for non-TV originated content<br />
|}<br />
<br />
<br />
'''Example Content Object'''<br />
<syntaxhighlight lang='json'>// create content object<br />
"content": {<br />
"type": "content",<br />
"assetid": "VIDEO-ID123",<br />
"isfullepisode": "y",<br />
"program": "Program Name",<br />
"title": "Episode Title S3 - EP1",<br />
"length": "1800",<br />
"segB": "Custom Segment B",<br />
"segC": "Custom Segment C",<br />
"crossId1": "Standard Episode ID",<br />
"crossId2": "Content Originator ID",<br />
"airdate": "20161013 20:00:00",<br />
"adloadtype": "2",<br />
"hasAds": "1", <br />
"progen": "CV"<br />
}</syntaxhighlight><br />
<br />
===== Ad Metadata =====<br />
The ad metadata should be passed for each individual ad.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| type || type of ad || <code>"preroll"</code>, <code>"midroll"</code>, or <code>"postroll"</code> || Yes<br />
|-<br />
| assetid || unique ID assigned to ad || custom || Yes<br />
|}<br />
<br />
===== Example Ad Object =====<br />
<syntaxhighlight lang="javascript"><br />
// create ad object<br />
"ad": {<br />
"type": "preroll",<br />
"assetid": "AD-ID123"<br />
}<br />
</syntaxhighlight><br />
<br />
=== Configure Payload: Events ===<br />
<br />
The last part of the payload is for enabling events so content is measured correctly when viewed. The events and required parameters are included below.<br />
<br />
==== Event Types ====<br />
<br />
The available events are:<br />
{| class="wikitable"<br />
|-<br />
! Event !! Description<br />
|-<br />
| <code>"playhead"</code> || Playhead position in seconds. Must be passed as a whole number every 10 seconds. The final playhead position should also be sent before an asset has changed to properly capture full duration. Playhead is used to handle pause and scrubbing. When content is paused, stop passing playhead position.<br />
|-<br />
| <code>"complete"</code> || Complete event must be sent when the content has completed full playback or if a user has initiated a Stop. Before calling the complete event, a final playhead update with the final position is required to be sent to receive full duration credit. Complete does not need to be called if a user transitions videos (e.g. channel change or selecting a new VOD asset) before the video completes.<br />
|-<br />
| <code>"delete"</code> || The delete event should be sent when the application is exited and the viewing session is completed. After 30 minutes of inactivity, the session will expire automatically. All creditable duration will be summarized for all asset types when delete occurs (content and ads). Note that the delete event may not be feasible on certain platforms that do not offer the necessary callbacks to trigger this event.<br />
|}<br />
<br />
===== Event Parameters =====<br />
<br />
The following parameters need to be passed when calling events:<br />
<br />
{| class="wikitable"<br />
|-<br />
! Parameter !! Description !! Value !! Required<br />
|-<br />
| <code>"event"</code> || event type || <code>"playhead"</code>, <code>"complete"</code>, or <code>"delete"</code> || Yes<br />
|-<br />
| <code>"position"</code> || creditable position || playhead position in seconds or UTC timestamp in seconds for livestream || Yes<br />
|-<br />
| <code>"type"</code> || asset type || <code>"content"</code>, <code>"ad"</code> || Yes<br />
|-<br />
| <code>"utc"</code> || Unix timestamp in milliseconds. Must be passed every 10 seconds. || <code>"1472760000000"</code> || Yes<br />
|}<br />
<br />
===== Example Event =====<br />
You can call events by passing values in the required parameters:<br />
<br />
<syntaxhighlight lang="javascript"><br />
"devInfo": [deviceInfo],<br />
"metadata": {<br />
"static": [static metadata],<br />
"content": [content metadata],<br />
"ad": [ad metadata]<br />
},<br />
// Event Parameters<br />
"event": [event], // event name<br />
"position": [playheadPosition], //position in seconds<br />
"type": [asset type], // values are "content" or "ad"<br />
"utc": "1472760000000" //unix timestamp in milliseconds<br />
}<br />
</syntaxhighlight><br />
<br />
'''Note:''' The full payload including "devInfo" and "metadata" must be populated in each event request.<br />
<br />
===== Sample Event Lifecycle =====<br />
The sample event lifecycle can be used as a reference for identifying the order for calling events and values to pass.<br />
<br />
<syntaxhighlight lang="javascript"><br />
// Start of Session: session ID created when App is opened<br />
<br />
// Preroll<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
// Content<br />
Content Playhead {"event": "playhead", "position": "0", "type": "content", "utc": "1472760000000"} <br />
<br />
// Midroll<br />
Midroll Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
// Content resumes at 15 minutes<br />
Content Playhead {"event": "playhead", "position": "900", "type": "content", "utc": "1472760000000"} <br />
<br />
// Content completes at 30 minutes<br />
Complete {"event": "complete", "position": "1800", "type": "content", "utc": "1472760000000"} <br />
<br />
// Postroll<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
//End of Session: The delete event should be called when the App is exited. The values for position and type not required to be passed.<br />
Delete { "event": "delete", "position": "", "type": "", "utc": "1472760000000"} <br />
</syntaxhighlight><br />
<br />
<br />
'''Sample Event Lifecycle - Detailed Storyline'''<br />
This detailed event sequence provides additional insight for the correct events to call when handling certain playback scenarios.<br />
<syntaxhighlight lang='javascript'>// SESSION STARTS<br />
// Start of Session: session ID created when App is opened<br />
<br />
// PREROLL<br />
// Preroll Start - Start each Ad with a position of "0", resetting to '0' for each Ad, and Ad break.<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
// Preroll Stop - End each Ad with the final position of the Ad.<br />
Ad Playhead {"event": "playhead", "position": "15", "type": "ad", "utc": "1472761500000"}<br />
<br />
// CONTENT <br />
// Content Start - Start new content streams with a position of "0" incrementing the position every 10 seconds.<br />
Content Playhead {"event": "playhead", "position": "0", "type": "content", "utc": "1472761500000"} <br />
<br />
// Content Stop Before Ad Break - Send a playhead update including the current content positon before an Ad break.<br />
Content Playhead {"event": "playhead", "position": "299", "type": "content", "utc": "1472787400000"}<br />
<br />
// MIDROLL<br />
// Midroll Start - Start each Ad with a position of "0", resetting to '0' for each Ad, and Ad break.<br />
Midroll Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472787500000"} <br />
<br />
// Midroll Stop - End each Ad with the final position of the Ad.<br />
Ad Playhead {"event": "playhead", "position": "60", "type": "ad", "utc": "1472793500000"}<br />
<br />
// CONTENT<br />
// Content resumes at 5 minutes - Send playhead update with the current resumed position, and begin incrimenting the positon every 10 seconds.<br />
Content Playhead {"event": "playhead", "position": "300", "type": "content", "utc": "1472799500000"} <br />
<br />
// Content completes at 10:12 - Make sure to send in the playhead event with the final content position before sending the complete event.<br />
Final Content Playhead {"event": "playhead", "position": "612", "type": "content", "utc": "1472830700000"} <br />
<br />
Complete {"event": "complete", "position": "612", "type": "content", "utc": "1472830800000"} <br />
<br />
// POSTROLL<br />
// Postroll Start - Start each Ad with a position of "0", resetting to '0' for each Ad, and Ad break.<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472830900000"} <br />
<br />
// Postroll Stop - End each Ad with the final position of the Ad.<br />
Ad Playhead {"event": "playhead", "position": "45", "type": "ad", "utc": "1472835300000"}<br />
<br />
// SESSION ENDS<br />
<br />
//End of Session: The delete event should be called when the App is exited. The values for position and type not required to be passed.<br />
Delete { "event": "delete", "position": "", "type": "", "utc": "1472835400000"} </syntaxhighlight><br />
<br />
<br />
=====Handling Playhead=====<br />
Calling <code>"playhead"</code> is critical for accurate duration crediting. You can reference the below guidance to determine the correct playhead position to pass depending on the playback scenario.<br />
<br />
'''Playhead: General'''<br />
* Playhead position must start at 0 for each new asset, and be passed at least every 10 seconds.<br />
* Final postion must be sent at the end of content or an ad<br />
<br />
'''Playhead: Ads'''<br />
* The final position must be sent when switching from content to ad, or ad to content.<br />
* Each ad playhead position should be 0 at ad start.<br />
* For Ad Pods, playhead must be called, and reset to 0 for each individual ad. <br />
* The last content position before an Ad should be sent before switching to Ads.<br />
* When content has resumed following an ad break, the playhead position update must continue where the previous content segment left off.<br />
<br />
'''Playhead: User Actions'''<br />
* Upon user scrubbing, the current position must be sent before a user scrubs, and the new position should be sent where the user lands, and begin sending in the 10 second updates thereafter.<br />
* On pause, send the current position and then discontinue sending playhead event updates.<br />
* If a user exits a stream early, the last current position must be sent in a playhead update to receive accurate duration.<br />
<br />
===== Interruption Scenarios =====<br />
<br />
As part of configuring events, you will need to handle all possible interruption scenarios such as:<br />
<br />
*Wi-Fi OFF / ON<br />
*App going Background / Foreground (Video players only, not for Audio players)<br />
*App Crash or Exit<br />
<br />
When playback is interrupted, the app needs to send delete immediately.<br />
<br />
Once playback resumes, a new session will need to be created with a unique session ID. All of the required metadata and events will need to be sent.<br />
<br />
'''Note:''' The session will automatically timeout after 30 minutes of inactivity.<br />
<br />
=== Example Request ===<br />
<br />
Now that we walked through the Cloud API integration steps, your requests should have the following components: Session ID, App ID, and Payload. You can reference the example below when your reviewing your integration.<br />
<br />
<syntaxhighlight lang="javascript"><br />
// 1. Create Session ID<br />
sessionID = Date.now()+String(Math.random()*1000000 >> 0); // Must be GUID for each viewing session<br />
<br />
// 2. Define URL Structure with App ID and Session ID<br />
sessionURL = https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=<br />
<br />
// 3. Configure Payload<br />
// 3.1 Configure Payload: devInfo <br />
payload = {<br />
"devInfo": {<br />
"devId": "AD-ID", <br />
"apn": "AppName",<br />
"apv": "1.0",<br />
"uoo": "false"<br />
},<br />
<br />
// 3.2 Configure Payload: metadata<br />
"metadata": {<br />
"static": {}, // object for measuring static content<br />
"content": { // object for measuring video content<br />
"type": "content", // "content" for video<br />
"assetid": "VIDEO-ID123", // unique ID for video<br />
"isfullepisode": "y", // full episode flag<br />
"program": "Program Name", // program name<br />
"title": "Episode Title S3 - EP1", // episode name<br />
"length": "1800", // content duration in seconds<br />
"segB": "Custom Segment B", // custom segment<br />
"segC": "Custom Segment C", // custom segment<br />
"crossId1": "Standard Episode ID", // episode ID<br />
"crossId2": "Content Originator ID", // content orginator (required for distributors)<br />
"airdate": "20161013 20:00:00", // airdate<br />
"adloadtype": "2", // ad load flag<br />
"hasAds": "1", // content contains ads = 1 / no ads = 0<br />
"progen": "CV" // program genre abbreviation<br />
},<br />
"ad": {<br />
"type": "preroll", // type of ad<br />
"assetid": "AD-ID123" // unique ID for ad<br />
}<br />
},<br />
<br />
// 3.3 Configure Payload: events<br />
"event": "playhead", //event name<br />
"position": "300", // position in seconds<br />
"type": "content", //"content" or "ad"<br />
"utc": "1456448742000" //unix timestamp in milliseconds <br />
}<br />
<br />
// Append payload to URL<br />
var image = new Image()<br />
image.onerror = function() {<br />
// wait and send again<br />
}<br />
(new Image).src = sessionURL+encodeURI(JSON.stringify(payload));<br />
</syntaxhighlight><br />
<br />
==== Enable Debug Logging ====<br />
<br />
Now that you have set up the Cloud API requests, you can enable debug logging to validate your integration. Enabling debug logging is required for Nielsen certification.<br />
<br />
==== GET Request ====<br />
<br />
Display GET Request to console using a name to identify each event (e.g. playhead).<br />
<br />
<syntaxhighlight lang="javascript"><br />
console.log("Event", image); <br />
</syntaxhighlight><br />
<br />
==== Payload ====<br />
<br />
Output payload to identify required metadata and events.<br />
<br />
<syntaxhighlight lang="javascript"><br />
console.log("Event Payload", payload); <br />
</syntaxhighlight><br />
<br />
==== HTTP Response Code ====<br />
<br />
Confirm request was completed by viewing HTTP response code.<br />
<br />
<syntaxhighlight lang="javascript"><br />
code = msg.GetResponseCode();<br />
console.log("Response Code", code); <br />
</syntaxhighlight><br />
<br />
You can reference the HTTP Response Code table when reviewing your requests:<br />
<br />
{| class="wikitable"<br />
|-<br />
! Status Code !! Status Text !! Description<br />
|-<br />
| <code>200</code> || OK || request received<br />
|-<br />
| <code>403</code> || Forbidden || invalid App ID<br />
|-<br />
| <code>404</code> || Not Found || JSON issue<br />
|}<br />
<br />
== Opt-Out ==<br />
Your app must provide a means for the user to Opt-Out, or Opt-In to Nielsen Measurement. This requirement can be fulfilled by checking the device OS for the user's setting of "Limit Ad Tracking" or similar option. If the device offers "Limit Ad Tracking" settings, you should set <code>uoo=true</code> or <code>uoo=false</code> depending on the user's privacy setting. Also, ensure that the <code>devId</code> is set to a blank value if the user elects to opt-out.<br />
<br />
If the device does not have OS-level "Do Not Track" settings, you can implement opt-out by creating an Opt-Out/Opt-In button, toggle switch, or slider within the app "Settings", or "About" section to allow the user selection.<br />
<br />
[[File:Nielsen Opt-Out.png|link=]]<br />
<br />
You will need to store the User Opt-Out (uoo) status, so that it can be retrieved and populated in the <code>"devInfo"</code> metadata which will be sent in every Cloud API event (playhead, complete, & delete). <br />
<br />
{| class="wikitable"<br />
|-<br />
! uoo Key !! Description !! Values<br />
|-<br />
| uoo || Device is Opted-In to Nielsen Measurement (Recommended Default Setting) || <code>"false"</code><br />
|-<br />
| uoo || Device is Opted-Out of Nielsen Measurement || <code>"true"</code><br />
|}<br />
<br />
===== devInfo Opt-In JSON Payload Example =====<br />
<br />
<syntaxhighlight lang="javascript"><br />
"devInfo": {<br />
"apn": "Cloud API Sample App",<br />
"apv": "1",<br />
"devId": "7be25cf9-8c40-5cc2-871e-19bf41940288",<br />
"uoo": "false"<br />
},<br />
</syntaxhighlight><br />
<br />
===== devInfo Opt-Out JSON Payload Example =====<br />
<br />
<syntaxhighlight lang="javascript"><br />
"devInfo": {<br />
"apn": "Cloud API Sample App",<br />
"apv": "1",<br />
"devId": "", //devId must be blank when a user elects to Opt-Out.<br />
"uoo": "true"<br />
},<br />
</syntaxhighlight><br />
<br />
===== Privacy Information Template To Include In Opt-Out Screen =====<br />
<br />
<br />
Additionally, all applications must display the Nielsen privacy policy within their application, typically in the Settings/About screen of your application. The Nielsen privacy policy text is listed below.<br />
<br />
<blockquote><br />
'''''ABOUT NIELSEN MEASUREMENT'''''<br />
<br />
Television and the way we watch it have come a long way since Nielsen began measuring TV audiences in 1950. Today, the ability to watch our favorite shows at any time and on multiple devices amplifies the need for exceptionally adept and flexible audience measurement capabilities.<br />
<br />
Consumers are changing with the times, and the same goes for us. As technology continues to evolve and media companies try new ways to attract viewers, understanding what consumers are watching — and what they're watching on — is more important than ever. Today, viewing video is a personal and mobile experience — anytime and anywhere. Our capabilities provide relevant metrics that are necessary to inform successful marketing and programming and drive continued growth. As a global information and measurement leader, we are committed to protecting the privacy and security of the data we collect, process and use. While our digital measurement products are not used to identify you in any way, they help us and our clients measure and analyze how consumers engage with media across online, mobile and emerging technologies, and offer insights into consumer behavior.<br />
<br />
'''''YOUR CHOICES'''''<br />
<br />
Nielsen believes that you should have a choice about whether to contribute to our research and insights. To opt out or opt in to Nielsen measurement, please toggle your "Limit Ad Tracking" (or similar setting) on your device, or make the appropriate selection within the application's settings. If you have this app on more than one device, you will need to opt out of this app on each device. To learn more about our digital measurement products and your choices in regard to them, please visit http://www.nielsen.com/digitalprivacy.<br />
<br />
'''''Disclosure Template'''''<br />
<br />
Additionally, you must update the App description information from the App Distribution Store with the Nielsen Measurement disclosure below:<br />
<br />
This app features Nielsen's proprietary measurement software which will allow you to contribute to market research, like Nielsen's TV Ratings. Please see http://www.nielsen.com/digitalprivacy for more information.<br />
</blockquote><br />
<br />
== Testing ==<br />
Before providing an app build to Nielsen for testing, it is important to run validation checks once you have enabled debug logging.<br />
<br />
=== Payload Validation ===<br />
<br />
Ensure that all of the required payload data is populating while testing several videos. The following areas are critical to measurement:<br />
*devInfo<br />
*Asset metadata for both content, and ads<br />
*Events<br />
*Opt-Out status<br />
<br />
=== Player Events ===<br />
Review event calls:<br />
<br />
==== playhead ====<br />
*Playhead position updates every 10 seconds starting at position '0' for each new asset.<br />
*Final playhead position is sent on content, or ad before switching between assets.<br />
*Content metadata remains constant throughout an episode, or clip play.<br />
*Ad metadata is populated appropriately for each individual ad.<br />
*Playhead position update resumes for content after an ad break, and resets to 0 for each individual ad.<br />
*For scrubbing, last current position should be sent while scrubbing occurs, and the new position should also be sent where the user scrubs to.<br />
*Exiting a stream early should execute the last current position in a playhead update to receive accurate duration.<br />
*Upon pause, the current position should be sent, and playhead updates should stop incrementing until resume play occurs.<br />
<br />
==== complete ====<br />
*Check that the complete event executes upon content complete after the final playhead update is sent<br />
*Do not execute the complete event for ads<br />
<br />
==== delete ====<br />
*Check to see that the delete event occurs upon app exit, if the platform has the necessary exit callback events.<br />
<br />
==== GET Request Format ====<br />
*Ensure that the event payloads are formatted in JSON<br />
*Check to see that each of the Cloud API GET requests are properly encoded<br />
<br />
==== HTTP Response ====<br />
*Make sure that each of the Cloud API Get requests are received by the Nielsen Cloud API properly through use of the HTTP Response Code outputs enabled in console.<br />
<br />
==== Opt-Out ====<br />
*Test the "uoo" key gets populated accurately for both Opt-In and Opt-Out selections by validating the Cloud API events called after the user Opt-Out/Opt-In selection.<br />
*Test that the devId field is populated with a blank value if a user has elected to Opt-Out. For example: "devId": "",<br />
*If the device supports "Limit Ad Tracking" or has device "Opt-Out" settings, test that uoo=true, and that devId is set to a blank value if enabled in the device settings.<br />
<br />
== Go Live ==<br />
After your integration has been certified, you will need to: Change Endpoint and Disable Logging.<br />
<br />
'''Change Endpoint:''' You will need to update to the production endpoint:<br />
<br />
*Testing: <code>https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/</code><br />
*Production: <code>https://cloudapi.imrworldwide.com/nmapi/v2/</code><br />
<br />
Your production URL structure should now be:<br />
<code>https://cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=[payload]</code><br />
<br />
'''Disable Logging:''' You can now disable debug logging</div>AlexGutierrezhttps://engineeringportal.nielsen.com//w/index.php?title=DCR_Video_%26_Static_Cloud_API&diff=2652DCR Video & Static Cloud API2018-04-16T21:32:46Z<p>AlexGutierrez: </p>
<hr />
<div>{{Breadcrumb|}} {{Breadcrumb|Digital}} {{Breadcrumb|DCR & DTVR}} {{CurrentBreadcrumb}}<br />
[[Category:Digital]]<br />
<br />
This guide shows you how to integrate the Nielsen Cloud API to enable Digital Content Ratings (DCR) measurement on your over-the-top (OTT) Apps.<br />
*For Roku Apps, please see [[DCR Video & Static Roku Cloud API]]<br />
*For Mobile Apps, please see [[DCR Video & Static Mobile Cloud API]]<br />
<br />
==Prerequisites==<br />
To get started, you will need a Nielsen App ID. The App ID is a unique ID assigned to your app. This will be provided to you upon starting the integration.<br />
<br />
<syntaxhighlight lang="javascript">XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX</syntaxhighlight><br />
<br />
==Integration==<br />
We will cover the steps for constructing the Cloud API Calls.<br />
<br />
===Request Overview===<br />
<br />
====URL Structure====<br />
<br />
The Cloud API Calls are HTTP GET Requests with the URL structure:<br />
<br />
<syntaxhighlight lang="javascript">[endpoint]/[appid]/[sessionID]/a?b=[payload]</syntaxhighlight><br />
<br />
The URL includes the following components:<br />
<br />
*<code>[endpoint]</code>: location of data collection environment<br />
*<code>[appid]</code>: provided App ID<br />
*<code>[sessionID]</code>: unique value for each user session<br />
*<code>[payload]</code>: metadata and events<br />
<br />
====Endpoint====<br />
<br />
There are endpoints for testing and production:<br />
<br />
*Testing: <code>https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/</code><br />
*Production: <code>https://cloudapi.imrworldwide.com/nmapi/v2/</code><br />
<br />
During testing, all calls should be pointed to the testing endpoint. We will review the update to the production endpoint during the Go Live section of this guide.<br />
<br />
====URL Example====<br />
As you move through the integration steps, you can reference the below URL structure with the expanded payload:<br />
<br />
<syntaxhighlight lang="javascript"><br />
https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=<br />
{<br />
"devInfo": [deviceInfo],<br />
"metadata": {<br />
"static": [static_metadata],<br />
"content": [content_metadata],<br />
"ad": [ad metadata]<br />
},<br />
"event": [event],<br />
"position": [playhead_position],<br />
"type": [asset type],<br />
"utc": [UTC]<br />
}<br />
</syntaxhighlight><br />
<br />
===Create Session ID===<br />
A unique Session ID must be created upon app launch and provided in the URL. This will allow measurement to occur for the entire duration that a user is within the app.<br />
<br />
The session ID must be a GUID that is passed with every request and remain consistent throughout each individual session. You can use a GUID or a unique random number as shown in the recommended sample code below:<br />
<br />
<syntaxhighlight lang="javascript">sessionID = Date.now()+String(Math.random()*1000000 >> 0);</syntaxhighlight><br />
<br />
Upon exiting the app, the session will need to be terminated using the delete event. Sessions will automatically expire after 30 minutes of cloud inactivity.<br />
<br />
===Define URL Structure===<br />
Define the URL structure using your provided <code>[appid]</code> and a unique <code>[sessionID]</code>.<br />
<br />
<syntaxhighlight lang="javascript">https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=[payload]</syntaxhighlight><br />
<br />
===Configure Payload===<br />
<br />
All Cloud API requests must contain the following payload data:<br />
<br />
*''devInfo'': device and app info<br />
*''metadata'': asset metadata<br />
*''event metadata'': type of event<br />
<br />
The payload can be passed through key-values using the Nielsen reserved keys. The specific keys and descriptions are highlighted in the tables included in this section.<br />
<br />
'''Payload Example'''<br />
<br />
The example below should be referenced when following the steps for configuring the request payload.<br />
<br />
<syntaxhighlight lang="javascript"><br />
// 3.1 Configure Payload: devInfo <br />
payload = {<br />
"devInfo": {<br />
"devId": "AD-ID", <br />
"apn": "AppName",<br />
"apv": "1.0",<br />
"uoo": "false"<br />
},<br />
<br />
// 3.2 Configure Payload: metadata<br />
"metadata": {<br />
"static": {}, // object for measuring static content<br />
"content": { // object for measuring video content<br />
"type": "content", // "content" for video<br />
"assetid": "VIDEO-ID123", // unique ID for video<br />
"isfullepisode": "y", // full episode flag<br />
"program": "Program Name", // program name<br />
"title": "Episode Title S3 - EP1", // episode name<br />
"length": "1800", // content duration in seconds<br />
"segB": "Custom Segment B", // custom segment<br />
"segC": "Custom Segment C", // custom segment<br />
"crossId1": "Standard Episode ID", // episode ID<br />
"crossId2": "Content Originator ID", // content orginator (required for distributors)<br />
"airdate": "20161013 20:00:00", // airdate<br />
"adloadtype": "2" //ad load flag<br />
"hasAds": "1", // content contains ads = 1 / no ads = 0<br />
"progen": "CV" // program genre abbreviation<br />
},<br />
"ad": {<br />
"type": "preroll", // type of ad<br />
"assetid": "AD-ID123" // unique ID for ad<br />
}<br />
},<br />
<br />
// 3.3 Configure Payload: events<br />
"event": "playhead", //event name<br />
"position": "300", // position in seconds<br />
"type": "content", //"content" or "ad"<br />
"utc": "1456448742000" //unix timestamp in milliseconds <br />
}<br />
</syntaxhighlight><br />
<br />
=====Configure Payload: devInfo=====<br />
An object <code>"devInfo"</code> will need to be created to capture App and Device information.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| devId || unique ID to identify user (e.g. Advertising ID, Roku Device ID) || custom || Yes<br />
|-<br />
| apn || app name || custom || Yes<br />
|-<br />
| apv || app build version || custom || Yes<br />
|-<br />
| uoo || device opt-out status || <code>"true"</code> or <code>"false"</code> || Yes<br />
|-<br />
|}<br />
<br />
'''Example devInfo Object'''<br />
<syntaxhighlight lang="javascript"><br />
// create devInfo object<br />
"devInfo": {<br />
"devId": "AD-ID",<br />
"apn": "AppName",<br />
"apv": "1.0",<br />
"uoo": "false"<br />
},<br />
</syntaxhighlight><br />
<br />
==== 3.2 Configure Payload: metadata ====<br />
Asset metadata can be passed through <code>"metadata"</code>. There are two asset types: <code>"content"</code> for video and <code>"ad"</code> for ads. The metadata received for each asset is used for classification and reporting.<br />
<br />
You will need to set up <code>"metadata"</code> objects for <code>"content"</code> and <code>"ad"</code> with the required Nielsen keys as shown in the sample code below.<br />
<br />
===== Content Metadata =====<br />
Content metadata should remain constant throughout the entirety of an episode/clip including when ads play.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| type || type of asset || <code>"content"</code> || Yes<br />
|-<br />
| assetid || unique ID assigned to asset || custom || Yes<br />
|-<br />
| program ||name of program (25 character limit) || custom || Yes<br />
|-<br />
| title ||name of program (25 character limit) || custom || Yes<br />
|-<br />
| length || length of content in seconds || <code>seconds</code> (86400 for live stream) || Yes<br />
|-<br />
| segB || custom segment B || custom || <br />
|-<br />
| segC || custom segment C || custom || <br />
|-<br />
| airdate || the airdate in the linear TV || YYYYMMDD HH24:MI:SS || Yes<br />
|-<br />
| isfullepisode || full episode flag || <code>"y"</code>- full episode, <code>"n"</code>- non full episode || Yes<br />
|-<br />
| crossId1 || standard episode ID || custom || Yes<br />
|-<br />
| crossId2 || content originator (only required for distributors) || Nielsen ||<br />
|-<br />
| adloadtype || type of ad load:<br />
<code>"1"</code> Linear – matches TV ad load<br />
<br />
<code>"2"</code> Dynamic – Dynamic Ad Insertion (DAI)<br />
|| <code>"2"</code> - DCR measures content with dynamic ads || Yes<br />
|-<br />
| hasAds || ads indicator<br />
<code>"1"</code>: ads included<br />
<br />
<code>"0"</code>: ads not included<br />
|| <code>"1"</code> or <code>"0"</code> || Yes<br />
|-<br />
| progen || Genre (required only for non-TV originated content). See [[DCR OTT Genre List]] for accepted values || <code>"CV"</code> for Comedy Variety || Required for non-TV originated content<br />
|}<br />
<br />
<br />
'''Example Content Object'''<br />
<syntaxhighlight lang='json'>// create content object<br />
"content": {<br />
"type": "content",<br />
"assetid": "VIDEO-ID123",<br />
"isfullepisode": "y",<br />
"program": "Program Name",<br />
"title": "Episode Title S3 - EP1",<br />
"length": "1800",<br />
"segB": "Custom Segment B",<br />
"segC": "Custom Segment C",<br />
"crossId1": "Standard Episode ID",<br />
"crossId2": "Content Originator ID",<br />
"airdate": "20161013 20:00:00",<br />
"adloadtype": "2",<br />
"hasAds": "1", <br />
"progen": "CV"<br />
}</syntaxhighlight><br />
<br />
===== Ad Metadata =====<br />
The ad metadata should be passed for each individual ad.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| type || type of ad || <code>"preroll"</code>, <code>"midroll"</code>, or <code>"postroll"</code> || Yes<br />
|-<br />
| assetid || unique ID assigned to ad || custom || Yes<br />
|}<br />
<br />
===== Example Ad Object =====<br />
<syntaxhighlight lang="javascript"><br />
// create ad object<br />
"ad": {<br />
"type": "preroll",<br />
"assetid": "AD-ID123"<br />
}<br />
</syntaxhighlight><br />
<br />
=== Configure Payload: Events ===<br />
<br />
The last part of the payload is for enabling events so content is measured correctly when viewed. The events and required parameters are included below.<br />
<br />
==== Event Types ====<br />
<br />
The available events are:<br />
{| class="wikitable"<br />
|-<br />
! Event !! Description<br />
|-<br />
| <code>"playhead"</code> || Playhead position in seconds. Must be passed as a whole number every 10 seconds. The final playhead position should also be sent before an asset has changed to properly capture full duration. Playhead is used to handle pause and scrubbing. When content is paused, stop passing playhead position.<br />
|-<br />
| <code>"complete"</code> || Complete event must be sent when the content has completed full playback or if a user has initiated a Stop. Before calling the complete event, a final playhead update with the final position is required to be sent to receive full duration credit. Complete does not need to be called if a user transitions videos (e.g. channel change or selecting a new VOD asset) before the video completes.<br />
|-<br />
| <code>"delete"</code> || The delete event should be sent when the application is exited and the viewing session is completed. After 30 minutes of inactivity, the session will expire automatically. All creditable duration will be summarized for all asset types when delete occurs (content and ads). Note that the delete event may not be feasible on certain platforms that do not offer the necessary callbacks to trigger this event.<br />
|}<br />
<br />
===== Event Parameters =====<br />
<br />
The following parameters need to be passed when calling events:<br />
<br />
{| class="wikitable"<br />
|-<br />
! Parameter !! Description !! Value !! Required<br />
|-<br />
| <code>"event"</code> || event type || <code>"playhead"</code>, <code>"complete"</code>, or <code>"delete"</code> || Yes<br />
|-<br />
| <code>"position"</code> || creditable position || playhead position in seconds or UTC timestamp in seconds for livestream || Yes<br />
|-<br />
| <code>"type"</code> || asset type || <code>"content"</code>, <code>"ad"</code> || Yes<br />
|-<br />
| <code>"utc"</code> || Unix timestamp in milliseconds. Must be passed every 10 seconds. || <code>"1472760000000"</code> || Yes<br />
|}<br />
<br />
===== Example Event =====<br />
You can call events by passing values in the required parameters:<br />
<br />
<syntaxhighlight lang="javascript"><br />
"devInfo": [deviceInfo],<br />
"metadata": {<br />
"static": [static metadata],<br />
"content": [content metadata],<br />
"ad": [ad metadata]<br />
},<br />
// Event Parameters<br />
"event": [event], // event name<br />
"position": [playheadPosition], //position in seconds<br />
"type": [asset type], // values are "content" or "ad"<br />
"utc": "1472760000000" //unix timestamp in milliseconds<br />
}<br />
</syntaxhighlight><br />
<br />
'''Note:''' The full payload including "devInfo" and "metadata" must be populated in each event request.<br />
<br />
===== Sample Event Lifecycle =====<br />
The sample event lifecycle can be used as a reference for identifying the order for calling events and values to pass.<br />
<br />
<syntaxhighlight lang="javascript"><br />
// Start of Session: session ID created when App is opened<br />
<br />
// Preroll<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
// Content<br />
Content Playhead {"event": "playhead", "position": "0", "type": "content", "utc": "1472760000000"} <br />
<br />
// Midroll<br />
Midroll Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
// Content resumes at 15 minutes<br />
Content Playhead {"event": "playhead", "position": "900", "type": "content", "utc": "1472760000000"} <br />
<br />
// Content completes at 30 minutes<br />
Complete {"event": "complete", "position": "1800", "type": "content", "utc": "1472760000000"} <br />
<br />
// Postroll<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
//End of Session: The delete event should be called when the App is exited. The values for position and type not required to be passed.<br />
Delete { "event": "delete", "position": "", "type": "", "utc": "1472760000000"} <br />
</syntaxhighlight><br />
<br />
<br />
'''Sample Event Lifecycle - Detailed Storyline'''<br />
This detailed event sequence provides additional insight for the correct events to call when handling certain playback scenarios.<br />
<syntaxhighlight lang='javascript'>// SESSION STARTS<br />
// Start of Session: session ID created when App is opened<br />
<br />
// PREROLL<br />
// Preroll Start - Start each Ad with a position of "0", resetting to '0' for each Ad, and Ad break.<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
// Preroll Stop - End each Ad with the final position of the Ad.<br />
Ad Playhead {"event": "playhead", "position": "15", "type": "ad", "utc": "1472761500000"}<br />
<br />
// CONTENT <br />
// Content Start - Start new content streams with a position of "0" incrementing the position every 10 seconds.<br />
Content Playhead {"event": "playhead", "position": "0", "type": "content", "utc": "1472761500000"} <br />
<br />
// Content Stop Before Ad Break - Send a playhead update including the current content positon before an Ad break.<br />
Content Playhead {"event": "playhead", "position": "299", "type": "content", "utc": "1472787400000"}<br />
<br />
// MIDROLL<br />
// Midroll Start - Start each Ad with a position of "0", resetting to '0' for each Ad, and Ad break.<br />
Midroll Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472787500000"} <br />
<br />
// Midroll Stop - End each Ad with the final position of the Ad.<br />
Ad Playhead {"event": "playhead", "position": "60", "type": "ad", "utc": "1472793500000"}<br />
<br />
// CONTENT<br />
// Content resumes at 5 minutes - Send playhead update with the current resumed position, and begin incrimenting the positon every 10 seconds.<br />
Content Playhead {"event": "playhead", "position": "300", "type": "content", "utc": "1472799500000"} <br />
<br />
// Content completes at 10:12 - Make sure to send in the playhead event with the final content position before sending the complete event.<br />
Final Content Playhead {"event": "playhead", "position": "612", "type": "content", "utc": "1472830700000"} <br />
<br />
Complete {"event": "complete", "position": "612", "type": "content", "utc": "1472830800000"} <br />
<br />
// POSTROLL<br />
// Postroll Start - Start each Ad with a position of "0", resetting to '0' for each Ad, and Ad break.<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472830900000"} <br />
<br />
// Postroll Stop - End each Ad with the final position of the Ad.<br />
Ad Playhead {"event": "playhead", "position": "45", "type": "ad", "utc": "1472835300000"}<br />
<br />
// SESSION ENDS<br />
<br />
//End of Session: The delete event should be called when the App is exited. The values for position and type not required to be passed.<br />
Delete { "event": "delete", "position": "", "type": "", "utc": "1472835400000"} </syntaxhighlight><br />
<br />
<br />
=====Handling Playhead=====<br />
Calling <code>"playhead"</code> is critical for accurate duration crediting. You can reference the below guidance to determine the correct playhead position to pass depending on the playback scenario.<br />
<br />
'''Playhead: General'''<br />
* Playhead position must start at 0 for each new asset, and be passed at least every 10 seconds.<br />
* Final postion must be sent at the end of content or an ad<br />
<br />
'''Playhead: Ads'''<br />
* The final position must be sent when switching from content to ad, or ad to content.<br />
* Each ad playhead position should be 0 at ad start.<br />
* For Ad Pods, playhead must be called, and reset to 0 for each individual ad. <br />
* The last content position before an Ad should be sent before switching to Ads.<br />
* When content has resumed following an ad break, the playhead position update must continue where the previous content segment left off.<br />
<br />
'''Playhead: User Actions'''<br />
* Upon user scrubbing, the current position must be sent before a user scrubs, and the new position should be sent where the user lands, and begin sending in the 10 second updates thereafter.<br />
* On pause, send the current position and then discontinue sending playhead event updates.<br />
* If a user exits a stream early, the last current position must be sent in a playhead update to receive accurate duration.<br />
<br />
===== Interruption Scenarios =====<br />
<br />
As part of configuring events, you will need to handle all possible interruption scenarios such as:<br />
<br />
*Wi-Fi OFF / ON<br />
*App going Background / Foreground (Video players only, not for Audio players)<br />
*App Crash or Exit<br />
<br />
When playback is interrupted, the app needs to send delete immediately.<br />
<br />
Once playback resumes, a new session will need to be created with a unique session ID. All of the required metadata and events will need to be sent.<br />
<br />
'''Note:''' The session will automatically timeout after 30 minutes of inactivity.<br />
<br />
=== Example Request ===<br />
<br />
Now that we walked through the Cloud API integration steps, your requests should have the following components: Session ID, App ID, and Payload. You can reference the example below when your reviewing your integration.<br />
<br />
<syntaxhighlight lang="javascript"><br />
// 1. Create Session ID<br />
sessionID = Date.now()+String(Math.random()*1000000 >> 0); // Must be GUID for each viewing session<br />
<br />
// 2. Define URL Structure with App ID and Session ID<br />
sessionURL = https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=<br />
<br />
// 3. Configure Payload<br />
// 3.1 Configure Payload: devInfo <br />
payload = {<br />
"devInfo": {<br />
"devId": "AD-ID", <br />
"apn": "AppName",<br />
"apv": "1.0",<br />
"uoo": "false"<br />
},<br />
<br />
// 3.2 Configure Payload: metadata<br />
"metadata": {<br />
"static": {}, // object for measuring static content<br />
"content": { // object for measuring video content<br />
"type": "content", // "content" for video<br />
"assetid": "VIDEO-ID123", // unique ID for video<br />
"isfullepisode": "y", // full episode flag<br />
"program": "Program Name", // program name<br />
"title": "Episode Title S3 - EP1", // episode name<br />
"length": "1800", // content duration in seconds<br />
"segB": "Custom Segment B", // custom segment<br />
"segC": "Custom Segment C", // custom segment<br />
"crossId1": "Standard Episode ID", // episode ID<br />
"crossId2": "Content Originator ID", // content orginator (required for distributors)<br />
"airdate": "20161013 20:00:00", // airdate<br />
"adloadtype": "2", // ad load flag<br />
"hasAds": "1", // content contains ads = 1 / no ads = 0<br />
"progen": "CV" // program genre abbreviation<br />
},<br />
"ad": {<br />
"type": "preroll", // type of ad<br />
"assetid": "AD-ID123" // unique ID for ad<br />
}<br />
},<br />
<br />
// 3.3 Configure Payload: events<br />
"event": "playhead", //event name<br />
"position": "300", // position in seconds<br />
"type": "content", //"content" or "ad"<br />
"utc": "1456448742000" //unix timestamp in milliseconds <br />
}<br />
<br />
// Append payload to URL<br />
var image = new Image()<br />
image.onerror = function() {<br />
// wait and send again<br />
}<br />
(new Image).src = sessionURL+encodeURI(JSON.stringify(payload));<br />
</syntaxhighlight><br />
<br />
==== Enable Debug Logging ====<br />
<br />
Now that you have set up the Cloud API requests, you can enable debug logging to validate your integration. Enabling debug logging is required for Nielsen certification.<br />
<br />
==== GET Request ====<br />
<br />
Display GET Request to console using a name to identify each event (e.g. playhead).<br />
<br />
<syntaxhighlight lang="javascript"><br />
console.log("Event", image); <br />
</syntaxhighlight><br />
<br />
==== Payload ====<br />
<br />
Output payload to identify required metadata and events.<br />
<br />
<syntaxhighlight lang="javascript"><br />
console.log("Event Payload", payload); <br />
</syntaxhighlight><br />
<br />
==== HTTP Response Code ====<br />
<br />
Confirm request was completed by viewing HTTP response code.<br />
<br />
<syntaxhighlight lang="javascript"><br />
code = msg.GetResponseCode();<br />
console.log("Response Code", code); <br />
</syntaxhighlight><br />
<br />
You can reference the HTTP Response Code table when reviewing your requests:<br />
<br />
{| class="wikitable"<br />
|-<br />
! Status Code !! Status Text !! Description<br />
|-<br />
| <code>200</code> || OK || request received<br />
|-<br />
| <code>403</code> || Forbidden || invalid App ID<br />
|-<br />
| <code>404</code> || Not Found || JSON issue<br />
|}<br />
<br />
== Opt-Out ==<br />
Your app must provide a means for the user to Opt-Out, or Opt-In to Nielsen Measurement. This requirement can be fulfilled by checking the device OS for the user's setting of "Limit Ad Tracking" or similar option. If the device offers "Limit Ad Tracking" settings, you should set <code>uoo=true</code> or <code>uoo=false</code> depending on the user's privacy setting. Also, ensure that the <code>devId</code> is set to a blank value if the user elects to opt-out.<br />
<br />
If the device does not have OS-level "Do Not Track" settings, you can implement opt-out by creating an Opt-Out/Opt-In button, toggle switch, or slider within the app "Settings", or "About" section to allow the user selection.<br />
<br />
[[File:Nielsen Opt-Out.png|link=]]<br />
<br />
You will need to store the User Opt-Out (uoo) status, so that it can be retrieved and populated in the <code>"devInfo"</code> metadata which will be sent in every Cloud API event (playhead, complete, & delete). <br />
<br />
{| class="wikitable"<br />
|-<br />
! uoo Key !! Description !! Values<br />
|-<br />
| uoo || Device is Opted-In to Nielsen Measurement (Recommended Default Setting) || <code>"false"</code><br />
|-<br />
| uoo || Device is Opted-Out of Nielsen Measurement || <code>"true"</code><br />
|}<br />
<br />
===== devInfo Opt-In JSON Payload Example =====<br />
<br />
<syntaxhighlight lang="javascript"><br />
"devInfo": {<br />
"apn": "Cloud API Sample App",<br />
"apv": "1",<br />
"devId": "7be25cf9-8c40-5cc2-871e-19bf41940288",<br />
"uoo": "false"<br />
},<br />
</syntaxhighlight><br />
<br />
===== devInfo Opt-Out JSON Payload Example =====<br />
<br />
<syntaxhighlight lang="javascript"><br />
"devInfo": {<br />
"apn": "Cloud API Sample App",<br />
"apv": "1",<br />
"devId": "", //devId must be blank when a user elects to Opt-Out.<br />
"uoo": "true"<br />
},<br />
</syntaxhighlight><br />
<br />
===== Privacy Information Template To Include In Opt-Out Screen =====<br />
<br />
<br />
Additionally, all applications must display the Nielsen privacy policy within their application, typically in the Settings/About screen of your application. The Nielsen privacy policy text is listed below.<br />
<br />
<blockquote><br />
'''''ABOUT NIELSEN MEASUREMENT'''''<br />
<br />
Television and the way we watch it have come a long way since Nielsen began measuring TV audiences in 1950. Today, the ability to watch our favorite shows at any time and on multiple devices amplifies the need for exceptionally adept and flexible audience measurement capabilities.<br />
<br />
Consumers are changing with the times, and the same goes for us. As technology continues to evolve and media companies try new ways to attract viewers, understanding what consumers are watching — and what they're watching on — is more important than ever. Today, viewing video is a personal and mobile experience — anytime and anywhere. Our capabilities provide relevant metrics that are necessary to inform successful marketing and programming and drive continued growth. As a global information and measurement leader, we are committed to protecting the privacy and security of the data we collect, process and use. While our digital measurement products are not used to identify you in any way, they help us and our clients measure and analyze how consumers engage with media across online, mobile and emerging technologies, and offer insights into consumer behavior.<br />
<br />
'''''YOUR CHOICES'''''<br />
<br />
Nielsen believes that you should have a choice about whether to contribute to our research and insights. To opt out, or opt into Nielsen measurement please toggle your "Limit Ad Tracking" (or similar setting) on your device. If you have this app on more than one device, you will need to opt out of this app on each device. To learn more about our digital measurement products and your choices in regard to them, please visit http://www.nielsen.com/digitalprivacy.<br />
<br />
'''''Disclosure Template'''''<br />
<br />
Additionally, you must update the App description information from the App Distribution Store with the Nielsen Measurement disclosure below:<br />
<br />
This app features Nielsen's proprietary measurement software which will allow you to contribute to market research, like Nielsen's TV Ratings. Please see http://www.nielsen.com/digitalprivacy for more information.<br />
</blockquote><br />
<br />
== Testing ==<br />
Before providing an app build to Nielsen for testing, it is important to run validation checks once you have enabled debug logging.<br />
<br />
=== Payload Validation ===<br />
<br />
Ensure that all of the required payload data is populating while testing several videos. The following areas are critical to measurement:<br />
*devInfo<br />
*Asset metadata for both content, and ads<br />
*Events<br />
*Opt-Out status<br />
<br />
=== Player Events ===<br />
Review event calls:<br />
<br />
==== playhead ====<br />
*Playhead position updates every 10 seconds starting at position '0' for each new asset.<br />
*Final playhead position is sent on content, or ad before switching between assets.<br />
*Content metadata remains constant throughout an episode, or clip play.<br />
*Ad metadata is populated appropriately for each individual ad.<br />
*Playhead position update resumes for content after an ad break, and resets to 0 for each individual ad.<br />
*For scrubbing, last current position should be sent while scrubbing occurs, and the new position should also be sent where the user scrubs to.<br />
*Exiting a stream early should execute the last current position in a playhead update to receive accurate duration.<br />
*Upon pause, the current position should be sent, and playhead updates should stop incrementing until resume play occurs.<br />
<br />
==== complete ====<br />
*Check that the complete event executes upon content complete after the final playhead update is sent<br />
*Do not execute the complete event for ads<br />
<br />
==== delete ====<br />
*Check to see that the delete event occurs upon app exit, if the platform has the necessary exit callback events.<br />
<br />
==== GET Request Format ====<br />
*Ensure that the event payloads are formatted in JSON<br />
*Check to see that each of the Cloud API GET requests are properly encoded<br />
<br />
==== HTTP Response ====<br />
*Make sure that each of the Cloud API Get requests are received by the Nielsen Cloud API properly through use of the HTTP Response Code outputs enabled in console.<br />
<br />
==== Opt-Out ====<br />
*Test the "uoo" key gets populated accurately for both Opt-In and Opt-Out selections by validating the Cloud API events called after the user Opt-Out/Opt-In selection.<br />
*Test that the devId field is populated with a blank value if a user has elected to Opt-Out. For example: "devId": "",<br />
*If the device supports "Limit Ad Tracking" or has device "Opt-Out" settings, test that uoo=true, and that devId is set to a blank value if enabled in the device settings.<br />
<br />
== Go Live ==<br />
After your integration has been certified, you will need to: Change Endpoint and Disable Logging.<br />
<br />
'''Change Endpoint:''' You will need to update to the production endpoint:<br />
<br />
*Testing: <code>https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/</code><br />
*Production: <code>https://cloudapi.imrworldwide.com/nmapi/v2/</code><br />
<br />
Your production URL structure should now be:<br />
<code>https://cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=[payload]</code><br />
<br />
'''Disable Logging:''' You can now disable debug logging</div>AlexGutierrezhttps://engineeringportal.nielsen.com//w/index.php?title=DCR_Video_%26_Static_Cloud_API&diff=2651DCR Video & Static Cloud API2018-04-16T21:31:10Z<p>AlexGutierrez: changes to clarify opt-out requirements</p>
<hr />
<div>{{Breadcrumb|}} {{Breadcrumb|Digital}} {{Breadcrumb|DCR & DTVR}} {{CurrentBreadcrumb}}<br />
[[Category:Digital]]<br />
<br />
This guide shows you how to integrate the Nielsen Cloud API to enable Digital Content Ratings (DCR) measurement on your over-the-top (OTT) Apps.<br />
*For Roku Apps, please see [[DCR Video & Static Roku Cloud API]]<br />
*For Mobile Apps, please see [[DCR Video & Static Mobile Cloud API]]<br />
<br />
==Prerequisites==<br />
To get started, you will need a Nielsen App ID. The App ID is a unique ID assigned to your app. This will be provided to you upon starting the integration.<br />
<br />
<syntaxhighlight lang="javascript">XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX</syntaxhighlight><br />
<br />
==Integration==<br />
We will cover the steps for constructing the Cloud API Calls.<br />
<br />
===Request Overview===<br />
<br />
====URL Structure====<br />
<br />
The Cloud API Calls are HTTP GET Requests with the URL structure:<br />
<br />
<syntaxhighlight lang="javascript">[endpoint]/[appid]/[sessionID]/a?b=[payload]</syntaxhighlight><br />
<br />
The URL includes the following components:<br />
<br />
*<code>[endpoint]</code>: location of data collection environment<br />
*<code>[appid]</code>: provided App ID<br />
*<code>[sessionID]</code>: unique value for each user session<br />
*<code>[payload]</code>: metadata and events<br />
<br />
====Endpoint====<br />
<br />
There are endpoints for testing and production:<br />
<br />
*Testing: <code>https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/</code><br />
*Production: <code>https://cloudapi.imrworldwide.com/nmapi/v2/</code><br />
<br />
During testing, all calls should be pointed to the testing endpoint. We will review the update to the production endpoint during the Go Live section of this guide.<br />
<br />
====URL Example====<br />
As you move through the integration steps, you can reference the below URL structure with the expanded payload:<br />
<br />
<syntaxhighlight lang="javascript"><br />
https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=<br />
{<br />
"devInfo": [deviceInfo],<br />
"metadata": {<br />
"static": [static_metadata],<br />
"content": [content_metadata],<br />
"ad": [ad metadata]<br />
},<br />
"event": [event],<br />
"position": [playhead_position],<br />
"type": [asset type],<br />
"utc": [UTC]<br />
}<br />
</syntaxhighlight><br />
<br />
===Create Session ID===<br />
A unique Session ID must be created upon app launch and provided in the URL. This will allow measurement to occur for the entire duration that a user is within the app.<br />
<br />
The session ID must be a GUID that is passed with every request and remain consistent throughout each individual session. You can use a GUID or a unique random number as shown in the recommended sample code below:<br />
<br />
<syntaxhighlight lang="javascript">sessionID = Date.now()+String(Math.random()*1000000 >> 0);</syntaxhighlight><br />
<br />
Upon exiting the app, the session will need to be terminated using the delete event. Sessions will automatically expire after 30 minutes of cloud inactivity.<br />
<br />
===Define URL Structure===<br />
Define the URL structure using your provided <code>[appid]</code> and a unique <code>[sessionID]</code>.<br />
<br />
<syntaxhighlight lang="javascript">https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=[payload]</syntaxhighlight><br />
<br />
===Configure Payload===<br />
<br />
All Cloud API requests must contain the following payload data:<br />
<br />
*''devInfo'': device and app info<br />
*''metadata'': asset metadata<br />
*''event metadata'': type of event<br />
<br />
The payload can be passed through key-values using the Nielsen reserved keys. The specific keys and descriptions are highlighted in the tables included in this section.<br />
<br />
'''Payload Example'''<br />
<br />
The example below should be referenced when following the steps for configuring the request payload.<br />
<br />
<syntaxhighlight lang="javascript"><br />
// 3.1 Configure Payload: devInfo <br />
payload = {<br />
"devInfo": {<br />
"devId": "AD-ID", <br />
"apn": "AppName",<br />
"apv": "1.0",<br />
"uoo": "false"<br />
},<br />
<br />
// 3.2 Configure Payload: metadata<br />
"metadata": {<br />
"static": {}, // object for measuring static content<br />
"content": { // object for measuring video content<br />
"type": "content", // "content" for video<br />
"assetid": "VIDEO-ID123", // unique ID for video<br />
"isfullepisode": "y", // full episode flag<br />
"program": "Program Name", // program name<br />
"title": "Episode Title S3 - EP1", // episode name<br />
"length": "1800", // content duration in seconds<br />
"segB": "Custom Segment B", // custom segment<br />
"segC": "Custom Segment C", // custom segment<br />
"crossId1": "Standard Episode ID", // episode ID<br />
"crossId2": "Content Originator ID", // content orginator (required for distributors)<br />
"airdate": "20161013 20:00:00", // airdate<br />
"adloadtype": "2" //ad load flag<br />
"hasAds": "1", // content contains ads = 1 / no ads = 0<br />
"progen": "CV" // program genre abbreviation<br />
},<br />
"ad": {<br />
"type": "preroll", // type of ad<br />
"assetid": "AD-ID123" // unique ID for ad<br />
}<br />
},<br />
<br />
// 3.3 Configure Payload: events<br />
"event": "playhead", //event name<br />
"position": "300", // position in seconds<br />
"type": "content", //"content" or "ad"<br />
"utc": "1456448742000" //unix timestamp in milliseconds <br />
}<br />
</syntaxhighlight><br />
<br />
=====Configure Payload: devInfo=====<br />
An object <code>"devInfo"</code> will need to be created to capture App and Device information.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| devId || unique ID to identify user (e.g. Advertising ID, Roku Device ID) || custom || Yes<br />
|-<br />
| apn || app name || custom || Yes<br />
|-<br />
| apv || app build version || custom || Yes<br />
|-<br />
| uoo || device opt-out status || <code>"true"</code> or <code>"false"</code> || Yes<br />
|-<br />
|}<br />
<br />
'''Example devInfo Object'''<br />
<syntaxhighlight lang="javascript"><br />
// create devInfo object<br />
"devInfo": {<br />
"devId": "AD-ID",<br />
"apn": "AppName",<br />
"apv": "1.0",<br />
"uoo": "false"<br />
},<br />
</syntaxhighlight><br />
<br />
==== 3.2 Configure Payload: metadata ====<br />
Asset metadata can be passed through <code>"metadata"</code>. There are two asset types: <code>"content"</code> for video and <code>"ad"</code> for ads. The metadata received for each asset is used for classification and reporting.<br />
<br />
You will need to set up <code>"metadata"</code> objects for <code>"content"</code> and <code>"ad"</code> with the required Nielsen keys as shown in the sample code below.<br />
<br />
===== Content Metadata =====<br />
Content metadata should remain constant throughout the entirety of an episode/clip including when ads play.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| type || type of asset || <code>"content"</code> || Yes<br />
|-<br />
| assetid || unique ID assigned to asset || custom || Yes<br />
|-<br />
| program ||name of program (25 character limit) || custom || Yes<br />
|-<br />
| title ||name of program (25 character limit) || custom || Yes<br />
|-<br />
| length || length of content in seconds || <code>seconds</code> (86400 for live stream) || Yes<br />
|-<br />
| segB || custom segment B || custom || <br />
|-<br />
| segC || custom segment C || custom || <br />
|-<br />
| airdate || the airdate in the linear TV || YYYYMMDD HH24:MI:SS || Yes<br />
|-<br />
| isfullepisode || full episode flag || <code>"y"</code>- full episode, <code>"n"</code>- non full episode || Yes<br />
|-<br />
| crossId1 || standard episode ID || custom || Yes<br />
|-<br />
| crossId2 || content originator (only required for distributors) || Nielsen ||<br />
|-<br />
| adloadtype || type of ad load:<br />
<code>"1"</code> Linear – matches TV ad load<br />
<br />
<code>"2"</code> Dynamic – Dynamic Ad Insertion (DAI)<br />
|| <code>"2"</code> - DCR measures content with dynamic ads || Yes<br />
|-<br />
| hasAds || ads indicator<br />
<code>"1"</code>: ads included<br />
<br />
<code>"0"</code>: ads not included<br />
|| <code>"1"</code> or <code>"0"</code> || Yes<br />
|-<br />
| progen || Genre (required only for non-TV originated content). See [[DCR OTT Genre List]] for accepted values || <code>"CV"</code> for Comedy Variety || Required for non-TV originated content<br />
|}<br />
<br />
<br />
'''Example Content Object'''<br />
<syntaxhighlight lang='json'>// create content object<br />
"content": {<br />
"type": "content",<br />
"assetid": "VIDEO-ID123",<br />
"isfullepisode": "y",<br />
"program": "Program Name",<br />
"title": "Episode Title S3 - EP1",<br />
"length": "1800",<br />
"segB": "Custom Segment B",<br />
"segC": "Custom Segment C",<br />
"crossId1": "Standard Episode ID",<br />
"crossId2": "Content Originator ID",<br />
"airdate": "20161013 20:00:00",<br />
"adloadtype": "2",<br />
"hasAds": "1", <br />
"progen": "CV"<br />
}</syntaxhighlight><br />
<br />
===== Ad Metadata =====<br />
The ad metadata should be passed for each individual ad.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| type || type of ad || <code>"preroll"</code>, <code>"midroll"</code>, or <code>"postroll"</code> || Yes<br />
|-<br />
| assetid || unique ID assigned to ad || custom || Yes<br />
|}<br />
<br />
===== Example Ad Object =====<br />
<syntaxhighlight lang="javascript"><br />
// create ad object<br />
"ad": {<br />
"type": "preroll",<br />
"assetid": "AD-ID123"<br />
}<br />
</syntaxhighlight><br />
<br />
=== Configure Payload: Events ===<br />
<br />
The last part of the payload is for enabling events so content is measured correctly when viewed. The events and required parameters are included below.<br />
<br />
==== Event Types ====<br />
<br />
The available events are:<br />
{| class="wikitable"<br />
|-<br />
! Event !! Description<br />
|-<br />
| <code>"playhead"</code> || Playhead position in seconds. Must be passed as a whole number every 10 seconds. The final playhead position should also be sent before an asset has changed to properly capture full duration. Playhead is used to handle pause and scrubbing. When content is paused, stop passing playhead position.<br />
|-<br />
| <code>"complete"</code> || Complete event must be sent when the content has completed full playback or if a user has initiated a Stop. Before calling the complete event, a final playhead update with the final position is required to be sent to receive full duration credit. Complete does not need to be called if a user transitions videos (e.g. channel change or selecting a new VOD asset) before the video completes.<br />
|-<br />
| <code>"delete"</code> || The delete event should be sent when the application is exited and the viewing session is completed. After 30 minutes of inactivity, the session will expire automatically. All creditable duration will be summarized for all asset types when delete occurs (content and ads). Note that the delete event may not be feasible on certain platforms that do not offer the necessary callbacks to trigger this event.<br />
|}<br />
<br />
===== Event Parameters =====<br />
<br />
The following parameters need to be passed when calling events:<br />
<br />
{| class="wikitable"<br />
|-<br />
! Parameter !! Description !! Value !! Required<br />
|-<br />
| <code>"event"</code> || event type || <code>"playhead"</code>, <code>"complete"</code>, or <code>"delete"</code> || Yes<br />
|-<br />
| <code>"position"</code> || creditable position || playhead position in seconds or UTC timestamp in seconds for livestream || Yes<br />
|-<br />
| <code>"type"</code> || asset type || <code>"content"</code>, <code>"ad"</code> || Yes<br />
|-<br />
| <code>"utc"</code> || Unix timestamp in milliseconds. Must be passed every 10 seconds. || <code>"1472760000000"</code> || Yes<br />
|}<br />
<br />
===== Example Event =====<br />
You can call events by passing values in the required parameters:<br />
<br />
<syntaxhighlight lang="javascript"><br />
"devInfo": [deviceInfo],<br />
"metadata": {<br />
"static": [static metadata],<br />
"content": [content metadata],<br />
"ad": [ad metadata]<br />
},<br />
// Event Parameters<br />
"event": [event], // event name<br />
"position": [playheadPosition], //position in seconds<br />
"type": [asset type], // values are "content" or "ad"<br />
"utc": "1472760000000" //unix timestamp in milliseconds<br />
}<br />
</syntaxhighlight><br />
<br />
'''Note:''' The full payload including "devInfo" and "metadata" must be populated in each event request.<br />
<br />
===== Sample Event Lifecycle =====<br />
The sample event lifecycle can be used as a reference for identifying the order for calling events and values to pass.<br />
<br />
<syntaxhighlight lang="javascript"><br />
// Start of Session: session ID created when App is opened<br />
<br />
// Preroll<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
// Content<br />
Content Playhead {"event": "playhead", "position": "0", "type": "content", "utc": "1472760000000"} <br />
<br />
// Midroll<br />
Midroll Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
// Content resumes at 15 minutes<br />
Content Playhead {"event": "playhead", "position": "900", "type": "content", "utc": "1472760000000"} <br />
<br />
// Content completes at 30 minutes<br />
Complete {"event": "complete", "position": "1800", "type": "content", "utc": "1472760000000"} <br />
<br />
// Postroll<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
//End of Session: The delete event should be called when the App is exited. The values for position and type not required to be passed.<br />
Delete { "event": "delete", "position": "", "type": "", "utc": "1472760000000"} <br />
</syntaxhighlight><br />
<br />
<br />
'''Sample Event Lifecycle - Detailed Storyline'''<br />
This detailed event sequence provides additional insight for the correct events to call when handling certain playback scenarios.<br />
<syntaxhighlight lang='javascript'>// SESSION STARTS<br />
// Start of Session: session ID created when App is opened<br />
<br />
// PREROLL<br />
// Preroll Start - Start each Ad with a position of "0", resetting to '0' for each Ad, and Ad break.<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
// Preroll Stop - End each Ad with the final position of the Ad.<br />
Ad Playhead {"event": "playhead", "position": "15", "type": "ad", "utc": "1472761500000"}<br />
<br />
// CONTENT <br />
// Content Start - Start new content streams with a position of "0" incrementing the position every 10 seconds.<br />
Content Playhead {"event": "playhead", "position": "0", "type": "content", "utc": "1472761500000"} <br />
<br />
// Content Stop Before Ad Break - Send a playhead update including the current content positon before an Ad break.<br />
Content Playhead {"event": "playhead", "position": "299", "type": "content", "utc": "1472787400000"}<br />
<br />
// MIDROLL<br />
// Midroll Start - Start each Ad with a position of "0", resetting to '0' for each Ad, and Ad break.<br />
Midroll Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472787500000"} <br />
<br />
// Midroll Stop - End each Ad with the final position of the Ad.<br />
Ad Playhead {"event": "playhead", "position": "60", "type": "ad", "utc": "1472793500000"}<br />
<br />
// CONTENT<br />
// Content resumes at 5 minutes - Send playhead update with the current resumed position, and begin incrimenting the positon every 10 seconds.<br />
Content Playhead {"event": "playhead", "position": "300", "type": "content", "utc": "1472799500000"} <br />
<br />
// Content completes at 10:12 - Make sure to send in the playhead event with the final content position before sending the complete event.<br />
Final Content Playhead {"event": "playhead", "position": "612", "type": "content", "utc": "1472830700000"} <br />
<br />
Complete {"event": "complete", "position": "612", "type": "content", "utc": "1472830800000"} <br />
<br />
// POSTROLL<br />
// Postroll Start - Start each Ad with a position of "0", resetting to '0' for each Ad, and Ad break.<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472830900000"} <br />
<br />
// Postroll Stop - End each Ad with the final position of the Ad.<br />
Ad Playhead {"event": "playhead", "position": "45", "type": "ad", "utc": "1472835300000"}<br />
<br />
// SESSION ENDS<br />
<br />
//End of Session: The delete event should be called when the App is exited. The values for position and type not required to be passed.<br />
Delete { "event": "delete", "position": "", "type": "", "utc": "1472835400000"} </syntaxhighlight><br />
<br />
<br />
=====Handling Playhead=====<br />
Calling <code>"playhead"</code> is critical for accurate duration crediting. You can reference the below guidance to determine the correct playhead position to pass depending on the playback scenario.<br />
<br />
'''Playhead: General'''<br />
* Playhead position must start at 0 for each new asset, and be passed at least every 10 seconds.<br />
* Final postion must be sent at the end of content or an ad<br />
<br />
'''Playhead: Ads'''<br />
* The final position must be sent when switching from content to ad, or ad to content.<br />
* Each ad playhead position should be 0 at ad start.<br />
* For Ad Pods, playhead must be called, and reset to 0 for each individual ad. <br />
* The last content position before an Ad should be sent before switching to Ads.<br />
* When content has resumed following an ad break, the playhead position update must continue where the previous content segment left off.<br />
<br />
'''Playhead: User Actions'''<br />
* Upon user scrubbing, the current position must be sent before a user scrubs, and the new position should be sent where the user lands, and begin sending in the 10 second updates thereafter.<br />
* On pause, send the current position and then discontinue sending playhead event updates.<br />
* If a user exits a stream early, the last current position must be sent in a playhead update to receive accurate duration.<br />
<br />
===== Interruption Scenarios =====<br />
<br />
As part of configuring events, you will need to handle all possible interruption scenarios such as:<br />
<br />
*Wi-Fi OFF / ON<br />
*App going Background / Foreground (Video players only, not for Audio players)<br />
*App Crash or Exit<br />
<br />
When playback is interrupted, the app needs to send delete immediately.<br />
<br />
Once playback resumes, a new session will need to be created with a unique session ID. All of the required metadata and events will need to be sent.<br />
<br />
'''Note:''' The session will automatically timeout after 30 minutes of inactivity.<br />
<br />
=== Example Request ===<br />
<br />
Now that we walked through the Cloud API integration steps, your requests should have the following components: Session ID, App ID, and Payload. You can reference the example below when your reviewing your integration.<br />
<br />
<syntaxhighlight lang="javascript"><br />
// 1. Create Session ID<br />
sessionID = Date.now()+String(Math.random()*1000000 >> 0); // Must be GUID for each viewing session<br />
<br />
// 2. Define URL Structure with App ID and Session ID<br />
sessionURL = https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=<br />
<br />
// 3. Configure Payload<br />
// 3.1 Configure Payload: devInfo <br />
payload = {<br />
"devInfo": {<br />
"devId": "AD-ID", <br />
"apn": "AppName",<br />
"apv": "1.0",<br />
"uoo": "false"<br />
},<br />
<br />
// 3.2 Configure Payload: metadata<br />
"metadata": {<br />
"static": {}, // object for measuring static content<br />
"content": { // object for measuring video content<br />
"type": "content", // "content" for video<br />
"assetid": "VIDEO-ID123", // unique ID for video<br />
"isfullepisode": "y", // full episode flag<br />
"program": "Program Name", // program name<br />
"title": "Episode Title S3 - EP1", // episode name<br />
"length": "1800", // content duration in seconds<br />
"segB": "Custom Segment B", // custom segment<br />
"segC": "Custom Segment C", // custom segment<br />
"crossId1": "Standard Episode ID", // episode ID<br />
"crossId2": "Content Originator ID", // content orginator (required for distributors)<br />
"airdate": "20161013 20:00:00", // airdate<br />
"adloadtype": "2", // ad load flag<br />
"hasAds": "1", // content contains ads = 1 / no ads = 0<br />
"progen": "CV" // program genre abbreviation<br />
},<br />
"ad": {<br />
"type": "preroll", // type of ad<br />
"assetid": "AD-ID123" // unique ID for ad<br />
}<br />
},<br />
<br />
// 3.3 Configure Payload: events<br />
"event": "playhead", //event name<br />
"position": "300", // position in seconds<br />
"type": "content", //"content" or "ad"<br />
"utc": "1456448742000" //unix timestamp in milliseconds <br />
}<br />
<br />
// Append payload to URL<br />
var image = new Image()<br />
image.onerror = function() {<br />
// wait and send again<br />
}<br />
(new Image).src = sessionURL+encodeURI(JSON.stringify(payload));<br />
</syntaxhighlight><br />
<br />
==== Enable Debug Logging ====<br />
<br />
Now that you have set up the Cloud API requests, you can enable debug logging to validate your integration. Enabling debug logging is required for Nielsen certification.<br />
<br />
==== GET Request ====<br />
<br />
Display GET Request to console using a name to identify each event (e.g. playhead).<br />
<br />
<syntaxhighlight lang="javascript"><br />
console.log("Event", image); <br />
</syntaxhighlight><br />
<br />
==== Payload ====<br />
<br />
Output payload to identify required metadata and events.<br />
<br />
<syntaxhighlight lang="javascript"><br />
console.log("Event Payload", payload); <br />
</syntaxhighlight><br />
<br />
==== HTTP Response Code ====<br />
<br />
Confirm request was completed by viewing HTTP response code.<br />
<br />
<syntaxhighlight lang="javascript"><br />
code = msg.GetResponseCode();<br />
console.log("Response Code", code); <br />
</syntaxhighlight><br />
<br />
You can reference the HTTP Response Code table when reviewing your requests:<br />
<br />
{| class="wikitable"<br />
|-<br />
! Status Code !! Status Text !! Description<br />
|-<br />
| <code>200</code> || OK || request received<br />
|-<br />
| <code>403</code> || Forbidden || invalid App ID<br />
|-<br />
| <code>404</code> || Not Found || JSON issue<br />
|}<br />
<br />
== Opt-Out ==<br />
Your app must provide a means for the user to Opt-Out, or Opt-In to Nielsen Measurement. This requirement can be fulfilled by checking the device OS for the user's setting of "Limit Ad Tracking" or similar option. If the device offers "Limit Ad Tracking" settings, you must set <code>uoo=true</code> or <code>uoo=false</code> depending on the user's privacy setting. Also, ensure that the <code>devId</code> is set to a blank value if the user elects to opt-out.<br />
<br />
If the device does not have OS-level "Do Not Track" settings, you can implement opt-out by creating an Opt-Out/Opt-In button, toggle switch, or slider within the app "Settings", or "About" section to allow the user selection.<br />
<br />
Additionally, all applications must display the Nielsen privacy policy within their application, typically in the Settings/About screen of your application. The Nielsen privacy policy text is listed below.<br />
<br />
[[File:Nielsen Opt-Out.png|link=]]<br />
<br />
You will need to store the User Opt-Out (uoo) status, so that it can be retrieved and populated in the <code>"devInfo"</code> metadata which will be sent in every Cloud API event (playhead, complete, & delete). <br />
<br />
{| class="wikitable"<br />
|-<br />
! uoo Key !! Description !! Values<br />
|-<br />
| uoo || Device is Opted-In to Nielsen Measurement (Recommended Default Setting) || <code>"false"</code><br />
|-<br />
| uoo || Device is Opted-Out of Nielsen Measurement || <code>"true"</code><br />
|}<br />
<br />
===== devInfo Opt-In JSON Payload Example =====<br />
<br />
<syntaxhighlight lang="javascript"><br />
"devInfo": {<br />
"apn": "Cloud API Sample App",<br />
"apv": "1",<br />
"devId": "7be25cf9-8c40-5cc2-871e-19bf41940288",<br />
"uoo": "false"<br />
},<br />
</syntaxhighlight><br />
<br />
===== devInfo Opt-Out JSON Payload Example =====<br />
<br />
<syntaxhighlight lang="javascript"><br />
"devInfo": {<br />
"apn": "Cloud API Sample App",<br />
"apv": "1",<br />
"devId": "", //devId must be blank when a user elects to Opt-Out.<br />
"uoo": "true"<br />
},<br />
</syntaxhighlight><br />
<br />
===== Privacy Information Template To Include In Opt-Out Screen =====<br />
<br />
<blockquote><br />
'''''ABOUT NIELSEN MEASUREMENT'''''<br />
<br />
Television and the way we watch it have come a long way since Nielsen began measuring TV audiences in 1950. Today, the ability to watch our favorite shows at any time and on multiple devices amplifies the need for exceptionally adept and flexible audience measurement capabilities.<br />
<br />
Consumers are changing with the times, and the same goes for us. As technology continues to evolve and media companies try new ways to attract viewers, understanding what consumers are watching — and what they're watching on — is more important than ever. Today, viewing video is a personal and mobile experience — anytime and anywhere. Our capabilities provide relevant metrics that are necessary to inform successful marketing and programming and drive continued growth. As a global information and measurement leader, we are committed to protecting the privacy and security of the data we collect, process and use. While our digital measurement products are not used to identify you in any way, they help us and our clients measure and analyze how consumers engage with media across online, mobile and emerging technologies, and offer insights into consumer behavior.<br />
<br />
'''''YOUR CHOICES'''''<br />
<br />
Nielsen believes that you should have a choice about whether to contribute to our research and insights. To opt out, or opt into Nielsen measurement please toggle your "Limit Ad Tracking" (or similar setting) on your device. If you have this app on more than one device, you will need to opt out of this app on each device. To learn more about our digital measurement products and your choices in regard to them, please visit http://www.nielsen.com/digitalprivacy.<br />
<br />
'''''Disclosure Template'''''<br />
<br />
Additionally, you must update the App description information from the App Distribution Store with the Nielsen Measurement disclosure below:<br />
<br />
This app features Nielsen's proprietary measurement software which will allow you to contribute to market research, like Nielsen's TV Ratings. Please see http://www.nielsen.com/digitalprivacy for more information.<br />
</blockquote><br />
<br />
== Testing ==<br />
Before providing an app build to Nielsen for testing, it is important to run validation checks once you have enabled debug logging.<br />
<br />
=== Payload Validation ===<br />
<br />
Ensure that all of the required payload data is populating while testing several videos. The following areas are critical to measurement:<br />
*devInfo<br />
*Asset metadata for both content, and ads<br />
*Events<br />
*Opt-Out status<br />
<br />
=== Player Events ===<br />
Review event calls:<br />
<br />
==== playhead ====<br />
*Playhead position updates every 10 seconds starting at position '0' for each new asset.<br />
*Final playhead position is sent on content, or ad before switching between assets.<br />
*Content metadata remains constant throughout an episode, or clip play.<br />
*Ad metadata is populated appropriately for each individual ad.<br />
*Playhead position update resumes for content after an ad break, and resets to 0 for each individual ad.<br />
*For scrubbing, last current position should be sent while scrubbing occurs, and the new position should also be sent where the user scrubs to.<br />
*Exiting a stream early should execute the last current position in a playhead update to receive accurate duration.<br />
*Upon pause, the current position should be sent, and playhead updates should stop incrementing until resume play occurs.<br />
<br />
==== complete ====<br />
*Check that the complete event executes upon content complete after the final playhead update is sent<br />
*Do not execute the complete event for ads<br />
<br />
==== delete ====<br />
*Check to see that the delete event occurs upon app exit, if the platform has the necessary exit callback events.<br />
<br />
==== GET Request Format ====<br />
*Ensure that the event payloads are formatted in JSON<br />
*Check to see that each of the Cloud API GET requests are properly encoded<br />
<br />
==== HTTP Response ====<br />
*Make sure that each of the Cloud API Get requests are received by the Nielsen Cloud API properly through use of the HTTP Response Code outputs enabled in console.<br />
<br />
==== Opt-Out ====<br />
*Test the "uoo" key gets populated accurately for both Opt-In and Opt-Out selections by validating the Cloud API events called after the user Opt-Out/Opt-In selection.<br />
*Test that the devId field is populated with a blank value if a user has elected to Opt-Out. For example: "devId": "",<br />
*If the device supports "Limit Ad Tracking" or has device "Opt-Out" settings, test that uoo=true, and that devId is set to a blank value if enabled in the device settings.<br />
<br />
== Go Live ==<br />
After your integration has been certified, you will need to: Change Endpoint and Disable Logging.<br />
<br />
'''Change Endpoint:''' You will need to update to the production endpoint:<br />
<br />
*Testing: <code>https://sandbox-cloudapi.imrworldwide.com/nmapi/v2/</code><br />
*Production: <code>https://cloudapi.imrworldwide.com/nmapi/v2/</code><br />
<br />
Your production URL structure should now be:<br />
<code>https://cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=[payload]</code><br />
<br />
'''Disable Logging:''' You can now disable debug logging</div>AlexGutierrezhttps://engineeringportal.nielsen.com//w/index.php?title=TV&diff=2631TV2018-03-26T21:49:38Z<p>AlexGutierrez: </p>
<hr />
<div>[[Category:TV]]<br />
{{Banner|Encoder Support}}<br />
<br />
<br />
{{Alert|'''Download:''' '''[https://engineeringportal.nielsen.com/w/downloads/tv/REMINDER_Spring_2018_DST.pdf Reminder: Daylight Savings Time (DST) changes March 11]''' <br> '''See also:''' '''[https://engineeringportal.nielsen.com/w/downloads/tv/Quick_Reference_Guide_Spring_2018_DST.pdf Daylight Savings Time Quick Reference Guide Spring 2018]''' }}<br />
<br />
<br />
{| class="wikitable"<br />
|-<br />
! style="width: 75px;" |<br />
! General Reference<br />
|-<br />
| rowspan="1" | {{SmallIcon|PDFIcon.png}}<br />
| '''[https://engineeringportal.nielsen.com/w/downloads/tv/Encoding/Encoding_Install_Config_Policy_Rev_G.pdf Nielsen Encoder Installation and Configuration Policy]'''<br />
|}<br />
<br />
<br />
{{CategoryIcon|EncodingIcon.png|TV Audio Encoding}}<br />
{| class="wikitable"<br />
|-<br />
! style="width: 75px;" |<br />
! style="width: 35%;" | Software<br />
! style="width: 45px;" | OS <br />
! Download <br />
|-<br />
| {{SmallIcon|CircuitIcon.png}}<br />
| '''[[NWE-3GA]]''' || {{OSIcon|WindowsIcon.png}} || <br />
{{TVDownloadRequestLink|name=NWE-3GA NWE-3G Software package Rev.2.01|dlid=984b2a9e4619097386b99517a0a6c3335502bf8d}}<br />
<br />
|-<br />
<br />
| {{SmallIcon|RackIcon.png}}<br />
| '''[[NWE-TS]]'''<br />
|| {{OSIcon|FirmwareIcon.png}} || <br />
{{TVDownloadRequestLink|name=NWE-TS Firmware 4.3.11|dlid=127b391378579f8a3a6af758da11d80fe0156dcc}}<br />
<br />
|-<br />
<br />
| {{SmallIcon|RackIcon.png}}<br />
| '''[[Multi-Channel Encoder]]'''<br />
|| {{OSIcon|FirmwareIcon.png}} || <br />
*[[Encoder Downloads| Multi-Channel Encoder v1.2.4]]<br />
<br />
|-<br />
<br />
| {{SmallIcon|VODIcon.png}}<br />
| '''[[VOD Content Encoder]]'''<br />
|| {{OSIcon|WindowsIcon.png}} || <br />
*[[Encoder Downloads| VOD Content Encoder]]<br />
<br />
|-<br />
<br />
| {{SmallIcon|ComputerWaveIcon.png}}<br />
| '''[[SpoTTrac®]]'''<br />
|| {{OSIcon|FirmwareIcon.png}} || <br />
*[[Encoder Downloads| SpoTTrac Firmware]]<br />
<br />
|-<br />
<br />
| {{SmallIcon|EncoderGrayIcon.png}}<br />
| '''[[NAVE II]]'''<br />
|| {{OSIcon|WindowsIcon.png}} || <br />
{{TVDownloadRequestLink|name=NAVE II Firmware v38|dlid=72952888470db8208722a929e21f48e2355e78f9}}<br />
{{TVDownloadRequestLink|name=NAVE II Manager|dlid=8e016c637144843e1eec9fd56f75c4bbb1e9789c}}<br />
<br />
|-<br />
<br />
| {{SmallIcon|EncoderGrayIcon.png}}<br />
| '''[[NAVE IIC]]'''<br />
|| {{OSIcon|WindowsIcon.png}} || <br />
{{TVDownloadRequestLink|name=NAVE IIC Firmware v3.4.15|dlid=d0bcfd020127e125349f0e6c46945f195b60ef25}}<br />
<br />
|}<br />
<br />
<br />
{{CategoryIcon|ID3Icon.png|ID3 Transcoding}}<br />
{| class="wikitable"<br />
|-<br />
! style="width: 75px;" |<br />
! style="width: 35%;" | Software<br />
! style="width: 45px;" | OS <br />
! Download <br />
<br />
|-<br />
<br />
|rowspan="3"| {{SmallIcon|ID3XcodeIcon.png}}<br />
|rowspan="3"| '''[[ID3 Transcoders]]'''<br />
|| {{OSIcon|WindowsIcon.png}} || <br />
{{TVDownloadRequestLink|name=PCM-to-ID3 SDK (Windows) v2.3|dlid=39fa064bfbfa9ab2fc741ed65fd08c5bd4e7af90}}<br />
{{TVDownloadRequestLink|name=PCM-to-ID3 SDK (Windows Visual Studio 2013) v2.3|dlid=e7f6b7dbdb270c5bfaacac4bb31293f6b982015b}}<br />
|-<br />
|| {{OSIcon|LinuxIcon.png}} || <br />
{{TVDownloadRequestLink|name=PCM-to-ID3 SDK (Linux x64 GLIBC 2.12) v2.3.2|dlid=086357737aba64f618ca9e443fc001e7a105e385}}<br />
{{TVDownloadRequestLink|name=PCM-to-ID3 SDK (Linux x64 GLIBC 2.17) v2.3.2|dlid=1f34832be523d324118c90ff47b61f9678be0632}}<br />
{{TVDownloadRequestLink|name=PCM-to-ID3 SDK (Linux x86 GLIBC 2.17) v2.3.2|dlid=b1194bc6bcf4656b0bef4a54439b52505b769d82}}<br />
|-<br />
|| {{OSIcon|macOSIcon.png}} || <br />
{{TVDownloadRequestLink|name=PCM-to-ID3 SDK (MacOS) v2.3|dlid=f006e38c9cc0772330cf10bac498b859446d6c35}}<br />
<br />
|-<br />
|rowspan="1"| {{SmallIcon|ID3DashIcon.png}}<br />
|rowspan="1"| '''[[ID3 in MPEG-DASH]]'''<br />
|| {{OSIcon|StaticIcon.png}} || <br />
*[https://engineeringportal.nielsen.com/w/downloads/tv/ID3%20Transcoding/ID3%20in%20MPEG-DASH/bbb-MPEG-DASH.zip Big Buck Bunny MPEG-DASH Nielsen Tagged]<br />
*[https://engineeringportal.nielsen.com/w/downloads/tv/ID3%20Transcoding/ID3%20in%20MPEG-DASH/nielsenconsumer-MPEG-DASH.zip Nielsen Consumer MPEG-DASH Nielsen Tagged]<br />
*[https://engineeringportal.nielsen.com/w/downloads/tv/ID3%20Transcoding/ID3%20in%20MPEG-DASH/nielsenxplatform-MPEG-DASH.zip Nielsen Cross Platform MPEG-DASH Nielsen Tagged]<br />
|-<br />
<br />
|rowspan="3"| {{SmallIcon|ID3ValidateIcon.png}} <br />
|rowspan="3"| '''[[PCM-to-ID3 Validator]]'''<br />
|| {{OSIcon|WindowsIcon.png}} || <br />
{{TVDownloadRequestLink|name=PCM-to-ID3 Validator (Windows) v1.8|dlid=c0a30ece3e45faa5ad53a49674ac8c425dd32c1c}}<br />
|-<br />
|| {{OSIcon|LinuxIcon.png}} || <br />
{{TVDownloadRequestLink|name=PCM-to-ID3 Validator (CentOS) v1.7|dlid=802dd59fdf167c934a02967afa90551b8c25e34f}}<br />
|-<br />
|| {{OSIcon|macOSIcon.png}} || <br />
{{TVDownloadRequestLink|name=PCM-to-ID3 Validator (MacOS) v1.7|dlid=047a326f9f720f8df69f06fd433878b22ec5d10b}}<br />
|}<br />
<br />
<br />
{{CategoryIcon|DiagnosisToolsIcon.png|Diagnostic Tools}}<br />
{| class="wikitable"<br />
|-<br />
! style="width: 75px;" |<br />
! style="width: 35%;" | Software<br />
! style="width: 45px;" | OS <br />
! Download <br />
<br />
|-<br />
<br />
| {{SmallIcon|NACATIcon.png}} <br />
|| '''[[Nielsen Audio Code Analysis Tool|Nielsen Audio Code Analysis Tool (NACAT)]]''' <br />
|| {{OSIcon|WindowsIcon.png}} || <br />
{{TVDownloadRequestLink|name=NACAT installer (Windows) v4.0.6|dlid=0c321b8d82c767de2e0463498d7e0abbfa85c7e6}}<br />
<br />
|-<br />
<br />
| {{SmallIcon|NDICEIcon.png}}<br />
|| '''[[Nielsen Digital Code Extractor]]'''<br />
|| {{OSIcon|WindowsIcon.png}} || <br />
{{TVDownloadRequestLink|name=Nielsen Digital Code Extractor Installer|dlid=b596f86b2c9887afd43c1414a7e46220b659e811}} <BR> (See special installation instructions in [[Nielsen Digital Code Extractor|guide]])<br />
<br />
|}</div>AlexGutierrezhttps://engineeringportal.nielsen.com//w/index.php?title=Digital&diff=2561Digital2018-03-08T23:10:16Z<p>AlexGutierrez: </p>
<hr />
<div>{{Banner|Device SDKs and Implementation Guides}}<br />
<br />
<br />
<br />
<div class="row"><br />
<div class="small-4 large-4 columns" style="text-align: center;">{{NavigationBox|DCR.png|DCR & DTVR}}</div><br />
<div class="small-4 large-4 columns" style="text-align: center;">{{NavigationBox|DAR.png|Digital Ad Ratings}}</div><br />
<div class="small-4 large-4 columns" style="text-align: center;">{{NavigationBox|DA.png|Digital Audio}}</div><br />
</div><br />
<br />
<br />
<div class="row"><br />
<br />
<div class="small-4 large-4 columns" style="text-align: center;">{{NavigationBox|International.png|International}}</div><br />
<div class="small-4 large-4 columns" style="text-align: center;">{{NavigationBox|DownloadIcon.png|Digital Downloads}}</div><br />
</div></div>AlexGutierrezhttps://engineeringportal.nielsen.com//w/index.php?title=DCR_Video_%26_Static_Cloud_API&diff=2478DCR Video & Static Cloud API2018-02-20T20:33:44Z<p>AlexGutierrez: opt-out updates to emphasize the use of OS-level opt out</p>
<hr />
<div>{{Breadcrumb|}} {{Breadcrumb|Digital}} {{Breadcrumb|DCR & DTVR}} {{CurrentBreadcrumb}}<br />
[[Category:Digital]]<br />
<br />
This guide shows you how to integrate the Nielsen Cloud API to enable Digital Content Ratings (DCR) measurement on your over-the-top (OTT) Apps.<br />
*For Roku Apps, please see [[DCR Video & Static Roku Cloud API]]<br />
*For Mobile Apps, please see [[DCR Video & Static Mobile Cloud API]]<br />
<br />
==Prerequisites==<br />
To get started, you will need a Nielsen App ID. The App ID is a unique ID assigned to your app. This will be provided to you upon starting the integration.<br />
<br />
<syntaxhighlight lang="javascript">XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX</syntaxhighlight><br />
<br />
==Integration==<br />
We will cover the steps for constructing the Cloud API Calls.<br />
<br />
===Request Overview===<br />
<br />
====URL Structure====<br />
<br />
The Cloud API Calls are HTTP GET Requests with the URL structure:<br />
<br />
<syntaxhighlight lang="javascript">[endpoint]/[appid]/[sessionID]/a?b=[payload]</syntaxhighlight><br />
<br />
The URL includes the following components:<br />
<br />
*<code>[endpoint]</code>: location of data collection environment<br />
*<code>[appid]</code>: provided App ID<br />
*<code>[sessionID]</code>: unique value for each user session<br />
*<code>[payload]</code>: metadata and events<br />
<br />
====Endpoint====<br />
<br />
There are endpoints for testing and production:<br />
<br />
*Testing: <code>http://sandbox.cloudapi.imrworldwide.com/nmapi/v2/</code><br />
*Production: <code>https://cloudapi.imrworldwide.com/nmapi/v2/</code><br />
<br />
During testing, all calls should be pointed to the testing endpoint. We will review the update to the production endpoint during the Go Live section of this guide.<br />
<br />
====URL Example====<br />
As you move through the integration steps, you can reference the below URL structure with the expanded payload:<br />
<br />
<syntaxhighlight lang="javascript"><br />
http://sandbox.cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=<br />
{<br />
"devInfo": [deviceInfo],<br />
"metadata": {<br />
"static": [static_metadata],<br />
"content": [content_metadata],<br />
"ad": [ad metadata]<br />
},<br />
"event": [event],<br />
"position": [playhead_position],<br />
"type": [asset type],<br />
"utc": [UTC]<br />
}<br />
</syntaxhighlight><br />
<br />
===Create Session ID===<br />
A unique Session ID must be created upon app launch and provided in the URL. This will allow measurement to occur for the entire duration that a user is within the app.<br />
<br />
The session ID must be a GUID that is passed with every request and remain consistent throughout each individual session. You can use an existing ID, a random number, or date.now() as shown in the recommended sample code below:<br />
<br />
<syntaxhighlight lang="javascript">sessionID = Date.now()+String(Math.random()*1000000 >> 0);</syntaxhighlight><br />
<br />
Upon exiting the app, the session will need to be terminated using the delete event. Sessions will automatically expire after 30 minutes of cloud inactivity.<br />
<br />
===Define URL Structure===<br />
Define the URL structure using your provided <code>[appid]</code> and a unique <code>[sessionID]</code>.<br />
<br />
<syntaxhighlight lang="javascript">http://sandbox.cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=[payload]</syntaxhighlight><br />
<br />
===Configure Payload===<br />
<br />
All Cloud API requests must contain the following payload data:<br />
<br />
*''devInfo'': device and app info<br />
*''metadata'': asset metadata<br />
*''event metadata'': type of event<br />
<br />
The payload can be passed through key-values using the Nielsen reserved keys. The specific keys and descriptions are highlighted in the tables included in this section.<br />
<br />
'''Payload Example'''<br />
<br />
The example below should be referenced when following the steps for configuring the request payload.<br />
<br />
<syntaxhighlight lang="javascript"><br />
// 3.1 Configure Payload: devInfo <br />
payload = {<br />
"devInfo": {<br />
"devId": "AD-ID", <br />
"apn": "AppName",<br />
"apv": "1.0",<br />
"uoo": "false"<br />
},<br />
<br />
// 3.2 Configure Payload: metadata<br />
"metadata": {<br />
"static": {}, // object for measuring static content<br />
"content": { // object for measuring video content<br />
"type": "content", // "content" for video<br />
"assetid": "VIDEO-ID123", // unique ID for video<br />
"isfullepisode": "y", // full episode flag<br />
"program": "Program Name", // program name<br />
"title": "Episode Title S3 - EP1", // episode name<br />
"length": "1800", // content duration in seconds<br />
"segB": "Custom Segment B", // custom segment<br />
"segC": "Custom Segment C", // custom segment<br />
"crossId1": "Standard Episode ID", // episode ID<br />
"crossId2": "Content Originator ID", // content orginator (required for distributors)<br />
"airdate": "20161013 20:00:00", // airdate<br />
"adloadtype": "2" //ad load flag<br />
"hasAds": "1", // content contains ads = 1 / no ads = 0<br />
"progen": "CV" // program genre abbreviation<br />
},<br />
"ad": {<br />
"type": "preroll", // type of ad<br />
"assetid": "AD-ID123" // unique ID for ad<br />
}<br />
},<br />
<br />
// 3.3 Configure Payload: events<br />
"event": "playhead", //event name<br />
"position": "300", // position in seconds<br />
"type": "content", //"content" or "ad"<br />
"utc": "1456448742000" //unix timestamp in milliseconds <br />
}<br />
</syntaxhighlight><br />
<br />
=====Configure Payload: devInfo=====<br />
An object <code>"devInfo"</code> will need to be created to capture App and Device information.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| devId || unique ID to identify user (e.g. Advertising ID, Roku Device ID) || custom || Yes<br />
|-<br />
| apn || app name || custom || Yes<br />
|-<br />
| apv || app build version || custom || Yes<br />
|-<br />
| uoo || device opt-out status || <code>"true"</code> or <code>"false"</code> || Yes<br />
|-<br />
|}<br />
<br />
'''Example devInfo Object'''<br />
<syntaxhighlight lang="javascript"><br />
// create devInfo object<br />
"devInfo": {<br />
"devId": "AD-ID",<br />
"apn": "AppName",<br />
"apv": "1.0",<br />
"uoo": "false"<br />
},<br />
</syntaxhighlight><br />
<br />
==== 3.2 Configure Payload: metadata ====<br />
Asset metadata can be passed through <code>"metadata"</code>. There are two asset types: <code>"content"</code> for video and <code>"ad"</code> for ads. The metadata received for each asset is used for classification and reporting.<br />
<br />
You will need to set up <code>"metadata"</code> objects for <code>"content"</code> and <code>"ad"</code> with the required Nielsen keys as shown in the sample code below.<br />
<br />
===== Content Metadata =====<br />
Content metadata should remain constant throughout the entirety of an episode/clip including when ads play.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| type || type of asset || <code>"content"</code> || Yes<br />
|-<br />
| assetid || unique ID assigned to asset || custom || Yes<br />
|-<br />
| program ||name of program (25 character limit) || custom || Yes<br />
|-<br />
| title ||name of program (25 character limit) || custom || Yes<br />
|-<br />
| length || length of content in seconds || <code>seconds</code> (86400 for live stream) || Yes<br />
|-<br />
| segB || custom segment B || custom || <br />
|-<br />
| segC || custom segment C || custom || <br />
|-<br />
| airdate || the airdate in the linear TV || YYYYMMDD HH24:MI:SS || Yes<br />
|-<br />
| isfullepisode || full episode flag || <code>"y"</code>- full episode, <code>"n"</code>- non full episode || Yes<br />
|-<br />
| crossId1 || standard episode ID || custom || Yes<br />
|-<br />
| crossId2 || content originator (only required for distributors) || Nielsen ||<br />
|-<br />
| adloadtype || type of ad load:<br />
<code>"1"</code> Linear – matches TV ad load<br />
<br />
<code>"2"</code> Dynamic – Dynamic Ad Insertion (DAI)<br />
|| <code>"2"</code> - DCR measures content with dynamic ads || Yes<br />
|-<br />
| hasAds || ads indicator<br />
<code>"1"</code>: ads included<br />
<br />
<code>"0"</code>: ads not included<br />
|| <code>"1"</code> or <code>"0"</code> || Yes<br />
|-<br />
| progen || Genre (required only for non-TV originated content). See [[DCR OTT Genre List]] for accepted values || <code>"CV"</code> for Comedy Variety || Required for non-TV originated content<br />
|}<br />
<br />
<br />
'''Example Content Object'''<br />
<syntaxhighlight lang='json'>// create content object<br />
"content": {<br />
"type": "content",<br />
"assetid": "VIDEO-ID123",<br />
"isfullepisode": "y",<br />
"program": "Program Name",<br />
"title": "Episode Title S3 - EP1",<br />
"length": "1800",<br />
"segB": "Custom Segment B",<br />
"segC": "Custom Segment C",<br />
"crossId1": "Standard Episode ID",<br />
"crossId2": "Content Originator ID",<br />
"airdate": "20161013 20:00:00",<br />
"adloadtype": "2",<br />
"hasAds": "1", <br />
"progen": "CV"<br />
}</syntaxhighlight><br />
<br />
===== Ad Metadata =====<br />
The ad metadata should be passed for each individual ad.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| type || type of ad || <code>"preroll"</code>, <code>"midroll"</code>, or <code>"postroll"</code> || Yes<br />
|-<br />
| assetid || unique ID assigned to ad || custom || Yes<br />
|}<br />
<br />
===== Example Ad Object =====<br />
<syntaxhighlight lang="javascript"><br />
// create ad object<br />
"ad": {<br />
"type": "preroll",<br />
"assetid": "AD-ID123"<br />
}<br />
</syntaxhighlight><br />
<br />
=== Configure Payload: Events ===<br />
<br />
The last part of the payload is for enabling events so content is measured correctly when viewed. The events and required parameters are included below.<br />
<br />
==== Event Types ====<br />
<br />
The available events are:<br />
{| class="wikitable"<br />
|-<br />
! Event !! Description<br />
|-<br />
| <code>"playhead"</code> || Playhead position in seconds. Must be passed as a whole number every 10 seconds. The final playhead position should also be sent before an asset has changed to properly capture full duration. Playhead is used to handle pause and scrubbing. When content is paused, stop passing playhead position.<br />
|-<br />
| <code>"complete"</code> || Complete event must be sent when the content has completed full playback or if a user has initiated a Stop. Before calling the complete event, a final playhead update with the final position is required to be sent to receive full duration credit. Complete does not need to be called if a user transitions videos (e.g. channel change or selecting a new VOD asset) before the video completes.<br />
|-<br />
| <code>"delete"</code> || The delete event should be sent when the application is exited and the viewing session is completed. After 30 minutes of inactivity, the session will expire automatically. All creditable duration will be summarized for all asset types when delete occurs (content and ads). Note that the delete event may not be feasible on certain platforms that do not offer the necessary callbacks to trigger this event.<br />
|}<br />
<br />
===== Event Parameters =====<br />
<br />
The following parameters need to be passed when calling events:<br />
<br />
{| class="wikitable"<br />
|-<br />
! Parameter !! Description !! Value !! Required<br />
|-<br />
| <code>"event"</code> || event type || <code>"playhead"</code>, <code>"complete"</code>, or <code>"delete"</code> || Yes<br />
|-<br />
| <code>"position"</code> || creditable position || playhead position in seconds or UTC timestamp in seconds for livestream || Yes<br />
|-<br />
| <code>"type"</code> || asset type || <code>"content"</code>, <code>"ad"</code> || Yes<br />
|-<br />
| <code>"utc"</code> || Unix timestamp in milliseconds. Must be passed every 10 seconds. || <code>"1472760000000"</code> || Yes<br />
|}<br />
<br />
===== Example Event =====<br />
You can call events by passing values in the required parameters:<br />
<br />
<syntaxhighlight lang="javascript"><br />
"devInfo": [deviceInfo],<br />
"metadata": {<br />
"static": [static metadata],<br />
"content": [content metadata],<br />
"ad": [ad metadata]<br />
},<br />
// Event Parameters<br />
"event": [event], // event name<br />
"position": [playheadPosition], //position in seconds<br />
"type": [asset type], // values are "content" or "ad"<br />
"utc": "1472760000000" //unix timestamp in milliseconds<br />
}<br />
</syntaxhighlight><br />
<br />
'''Note:''' The full payload including "devInfo" and "metadata" must be populated in each event request.<br />
<br />
===== Sample Event Lifecycle =====<br />
The sample event lifecycle can be used as a reference for identifying the order for calling events and values to pass.<br />
<br />
<syntaxhighlight lang="javascript"><br />
// Start of Session: session ID created when App is opened<br />
<br />
// Preroll<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
// Content<br />
Content Playhead {"event": "playhead", "position": "0", "type": "content", "utc": "1472760000000"} <br />
<br />
// Midroll<br />
Midroll Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
// Content resumes at 15 minutes<br />
Content Playhead {"event": "playhead", "position": "900", "type": "content", "utc": "1472760000000"} <br />
<br />
// Content completes at 30 minutes<br />
Complete {"event": "complete", "position": "1800", "type": "content", "utc": "1472760000000"} <br />
<br />
// Postroll<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
//End of Session: The delete event should be called when the App is exited. The values for position and type not required to be passed.<br />
Delete { "event": "delete", "position": "", "type": "", "utc": "1472760000000"} <br />
</syntaxhighlight><br />
<br />
<br />
'''Sample Event Lifecycle - Detailed Storyline'''<br />
This detailed event sequence provides additional insight for the correct events to call when handling certain playback scenarios.<br />
<syntaxhighlight lang='javascript'>// SESSION STARTS<br />
// Start of Session: session ID created when App is opened<br />
<br />
// PREROLL<br />
// Preroll Start - Start each Ad with a position of "0", resetting to '0' for each Ad, and Ad break.<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
// Preroll Stop - End each Ad with the final position of the Ad.<br />
Ad Playhead {"event": "playhead", "position": "15", "type": "ad", "utc": "1472761500000"}<br />
<br />
// CONTENT <br />
// Content Start - Start new content streams with a position of "0" incrementing the position every 10 seconds.<br />
Content Playhead {"event": "playhead", "position": "0", "type": "content", "utc": "1472761500000"} <br />
<br />
// Content Stop Before Ad Break - Send a playhead update including the current content positon before an Ad break.<br />
Content Playhead {"event": "playhead", "position": "299", "type": "content", "utc": "1472787400000"}<br />
<br />
// MIDROLL<br />
// Midroll Start - Start each Ad with a position of "0", resetting to '0' for each Ad, and Ad break.<br />
Midroll Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472787500000"} <br />
<br />
// Midroll Stop - End each Ad with the final position of the Ad.<br />
Ad Playhead {"event": "playhead", "position": "60", "type": "ad", "utc": "1472793500000"}<br />
<br />
// CONTENT<br />
// Content resumes at 5 minutes - Send playhead update with the current resumed position, and begin incrimenting the positon every 10 seconds.<br />
Content Playhead {"event": "playhead", "position": "300", "type": "content", "utc": "1472799500000"} <br />
<br />
// Content completes at 10:12 - Make sure to send in the playhead event with the final content position before sending the complete event.<br />
Final Content Playhead {"event": "playhead", "position": "612", "type": "content", "utc": "1472830700000"} <br />
<br />
Complete {"event": "complete", "position": "612", "type": "content", "utc": "1472830800000"} <br />
<br />
// POSTROLL<br />
// Postroll Start - Start each Ad with a position of "0", resetting to '0' for each Ad, and Ad break.<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472830900000"} <br />
<br />
// Postroll Stop - End each Ad with the final position of the Ad.<br />
Ad Playhead {"event": "playhead", "position": "45", "type": "ad", "utc": "1472835300000"}<br />
<br />
// SESSION ENDS<br />
<br />
//End of Session: The delete event should be called when the App is exited. The values for position and type not required to be passed.<br />
Delete { "event": "delete", "position": "", "type": "", "utc": "1472835400000"} </syntaxhighlight><br />
<br />
<br />
=====Handling Playhead=====<br />
Calling <code>"playhead"</code> is critical for accurate duration crediting. You can reference the below guidance to determine the correct playhead position to pass depending on the playback scenario.<br />
<br />
'''Playhead: General'''<br />
* Playhead position must start at 0 for each new asset, and be passed at least every 10 seconds.<br />
* Final postion must be sent at the end of content or an ad<br />
<br />
'''Playhead: Ads'''<br />
* The final position must be sent when switching from content to ad, or ad to content.<br />
* Each ad playhead position should be 0 at ad start.<br />
* For Ad Pods, playhead must be called, and reset to 0 for each individual ad. <br />
* The last content position before an Ad should be sent before switching to Ads.<br />
* When content has resumed following an ad break, the playhead position update must continue where the previous content segment left off.<br />
<br />
'''Playhead: User Actions'''<br />
* Upon user scrubbing, the current position must be sent before a user scrubs, and the new position should be sent where the user lands, and begin sending in the 10 second updates thereafter.<br />
* On pause, send the current position and then discontinue sending playhead event updates.<br />
* If a user exits a stream early, the last current position must be sent in a playhead update to receive accurate duration.<br />
<br />
===== Interruption Scenarios =====<br />
<br />
As part of configuring events, you will need to handle all possible interruption scenarios such as:<br />
<br />
*Wi-Fi OFF / ON<br />
*App going Background / Foreground (Video players only, not for Audio players)<br />
*App Crash or Exit<br />
<br />
When playback is interrupted, the app needs to send delete immediately.<br />
<br />
Once playback resumes, a new session will need to be created with a unique session ID. All of the required metadata and events will need to be sent.<br />
<br />
'''Note:''' The session will automatically timeout after 30 minutes of inactivity.<br />
<br />
=== Example Request ===<br />
<br />
Now that we walked through the Cloud API integration steps, your requests should have the following components: Session ID, App ID, and Payload. You can reference the example below when your reviewing your integration.<br />
<br />
<syntaxhighlight lang="javascript"><br />
// 1. Create Session ID<br />
sessionID = Date.now()+String(Math.random()*1000000 >> 0); // Must be GUID for each viewing session<br />
<br />
// 2. Define URL Structure with App ID and Session ID<br />
sessionURL = http://sandbox.cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=<br />
<br />
// 3. Configure Payload<br />
// 3.1 Configure Payload: devInfo <br />
payload = {<br />
"devInfo": {<br />
"devId": "AD-ID", <br />
"apn": "AppName",<br />
"apv": "1.0",<br />
"uoo": "false"<br />
},<br />
<br />
// 3.2 Configure Payload: metadata<br />
"metadata": {<br />
"static": {}, // object for measuring static content<br />
"content": { // object for measuring video content<br />
"type": "content", // "content" for video<br />
"assetid": "VIDEO-ID123", // unique ID for video<br />
"isfullepisode": "y", // full episode flag<br />
"program": "Program Name", // program name<br />
"title": "Episode Title S3 - EP1", // episode name<br />
"length": "1800", // content duration in seconds<br />
"segB": "Custom Segment B", // custom segment<br />
"segC": "Custom Segment C", // custom segment<br />
"crossId1": "Standard Episode ID", // episode ID<br />
"crossId2": "Content Originator ID", // content orginator (required for distributors)<br />
"airdate": "20161013 20:00:00", // airdate<br />
"adloadtype": "2", // ad load flag<br />
"hasAds": "1", // content contains ads = 1 / no ads = 0<br />
"progen": "CV" // program genre abbreviation<br />
},<br />
"ad": {<br />
"type": "preroll", // type of ad<br />
"assetid": "AD-ID123" // unique ID for ad<br />
}<br />
},<br />
<br />
// 3.3 Configure Payload: events<br />
"event": "playhead", //event name<br />
"position": "300", // position in seconds<br />
"type": "content", //"content" or "ad"<br />
"utc": "1456448742000" //unix timestamp in milliseconds <br />
}<br />
<br />
// Append payload to URL<br />
var image = new Image()<br />
image.onerror = function() {<br />
// wait and send again<br />
}<br />
(new Image).src = sessionURL+encodeURI(JSON.stringify(payload));<br />
</syntaxhighlight><br />
<br />
==== Enable Debug Logging ====<br />
<br />
Now that you have set up the Cloud API requests, you can enable debug logging to validate your integration. Enabling debug logging is required for Nielsen certification.<br />
<br />
==== GET Request ====<br />
<br />
Display GET Request to console using a name to identify each event (e.g. playhead).<br />
<br />
<syntaxhighlight lang="javascript"><br />
console.log("Event", image); <br />
</syntaxhighlight><br />
<br />
==== Payload ====<br />
<br />
Output payload to identify required metadata and events.<br />
<br />
<syntaxhighlight lang="javascript"><br />
console.log("Event Payload", payload); <br />
</syntaxhighlight><br />
<br />
==== HTTP Response Code ====<br />
<br />
Confirm request was completed by viewing HTTP response code.<br />
<br />
<syntaxhighlight lang="javascript"><br />
code = msg.GetResponseCode();<br />
console.log("Response Code", code); <br />
</syntaxhighlight><br />
<br />
You can reference the HTTP Response Code table when reviewing your requests:<br />
<br />
{| class="wikitable"<br />
|-<br />
! Status Code !! Status Text !! Description<br />
|-<br />
| <code>200</code> || OK || request received<br />
|-<br />
| <code>403</code> || Forbidden || invalid App ID<br />
|-<br />
| <code>404</code> || Not Found || JSON issue<br />
|}<br />
<br />
== Opt-Out ==<br />
Your app must provide a means for the user to Opt-Out, or Opt-In to Nielsen Measurement. This requirement can be fulfilled by checking the device OS for the user's setting of "Limit Ad Tracking" or similar option. If the device offers "Limit Ad Tracking" settings, you must set uoo=true or uoo=false depending on the user's privacy setting. Also, ensure that the devId is set to a blank value if the user elects to opt-out.<br />
<br />
If the device does not have OS-level "Do Not Track" settings, you can implement opt-out by creating an Opt-Out/Opt-In button, toggle switch, or slider within the app "Settings", or "About" section to allow the user selection. If you have the ability to render text within the Opt-Out screen, it is recommended to add the Privacy Information included below.<br />
<br />
[[File:Nielsen Opt-Out.png|link=]]<br />
<br />
You will need to store the User Opt-Out (uoo) status, so that it can be retrieved and populated in the <code>"devInfo"</code> metadata which will be sent in every Cloud API event (playhead, complete, & delete). <br />
<br />
{| class="wikitable"<br />
|-<br />
! uoo Key !! Description !! Values<br />
|-<br />
| uoo || Device is Opted-In to Nielsen Measurement (Recommended Default Setting) || <code>"false"</code><br />
|-<br />
| uoo || Device is Opted-Out of Nielsen Measurement || <code>"true"</code><br />
|}<br />
<br />
===== devInfo Opt-In JSON Payload Example =====<br />
<br />
<syntaxhighlight lang="javascript"><br />
"devInfo": {<br />
"apn": "Roku Sample App",<br />
"apv": "1",<br />
"devId": "7be25cf9-8c40-5cc2-871e-19bf41940288",<br />
"uoo": "false"<br />
},<br />
</syntaxhighlight><br />
<br />
===== devInfo Opt-Out JSON Payload Example =====<br />
<br />
<syntaxhighlight lang="javascript"><br />
"devInfo": {<br />
"apn": "Roku Sample App",<br />
"apv": "1",<br />
"devId": "", //devId must be blank when a user elects to Opt-Out.<br />
"uoo": "true"<br />
},<br />
</syntaxhighlight><br />
<br />
===== Privacy Information Template To Include In Opt-Out Screen =====<br />
<br />
<blockquote><br />
'''''ABOUT NIELSEN MEASUREMENT'''''<br />
<br />
Television and the way we watch it have come a long way since Nielsen began measuring TV audiences in 1950. Today, the ability to watch our favorite shows at any time and on multiple devices amplifies the need for exceptionally adept and flexible audience measurement capabilities.<br />
<br />
Consumers are changing with the times, and the same goes for us. As technology continues to evolve and media companies try new ways to attract viewers, understanding what consumers are watching — and what they're watching on — is more important than ever. Today, viewing video is a personal and mobile experience — anytime and anywhere. Our capabilities provide relevant metrics that are necessary to inform successful marketing and programming and drive continued growth. As a global information and measurement leader, we are committed to protecting the privacy and security of the data we collect, process and use. While our digital measurement products are not used to identify you in any way, they help us and our clients measure and analyze how consumers engage with media across online, mobile and emerging technologies, and offer insights into consumer behavior.<br />
<br />
'''''YOUR CHOICES'''''<br />
<br />
Nielsen believes that you should have a choice about whether to contribute to our research and insights. To opt out, or opt into Nielsen measurement please toggle your "Limit Ad Tracking" (or similar setting) on your device. If you have this app on more than one device, you will need to opt out of this app on each device. To learn more about our digital measurement products and your choices in regard to them, please visit http://www.nielsen.com/digitalprivacy.<br />
<br />
'''''Disclosure Template'''''<br />
<br />
Additionally, you must update the App description information from the App Distribution Store with the Nielsen Measurement disclosure below:<br />
<br />
This app features Nielsen's proprietary measurement software which will allow you to contribute to market research, like Nielsen's TV Ratings. Please see http://www.nielsen.com/digitalprivacy for more information.<br />
</blockquote><br />
<br />
== Testing ==<br />
Before providing an app build to Nielsen for testing, it is important to run validation checks once you have enabled debug logging.<br />
<br />
=== Payload Validation ===<br />
<br />
Ensure that all of the required payload data is populating while testing several videos. The following areas are critical to measurement:<br />
*devInfo<br />
*Asset metadata for both content, and ads<br />
*Events<br />
*Opt-Out status<br />
<br />
=== Player Events ===<br />
Review event calls:<br />
<br />
==== playhead ====<br />
*Playhead position updates every 10 seconds starting at position '0' for each new asset.<br />
*Final playhead position is sent on content, or ad before switching between assets.<br />
*Content metadata remains constant throughout an episode, or clip play.<br />
*Ad metadata is populated appropriately for each individual ad.<br />
*Playhead position update resumes for content after an ad break, and resets to 0 for each individual ad.<br />
*For scrubbing, last current position should be sent while scrubbing occurs, and the new position should also be sent where the user scrubs to.<br />
*Exiting a stream early should execute the last current position in a playhead update to receive accurate duration.<br />
*Upon pause, the current position should be sent, and playhead updates should stop incrementing until resume play occurs.<br />
<br />
==== complete ====<br />
*Check that the complete event executes upon content complete after the final playhead update is sent<br />
*Do not execute the complete event for ads<br />
<br />
==== delete ====<br />
*Check to see that the delete event occurs upon app exit, if the platform has the necessary exit callback events.<br />
<br />
==== GET Request Format ====<br />
*Ensure that the event payloads are formatted in JSON<br />
*Check to see that each of the Cloud API GET requests are properly encoded<br />
<br />
==== HTTP Response ====<br />
*Make sure that each of the Cloud API Get requests are received by the Nielsen Cloud API properly through use of the HTTP Response Code outputs enabled in console.<br />
<br />
==== Opt-Out ====<br />
*Test the "uoo" key gets populated accurately for both Opt-In and Opt-Out selections by validating the Cloud API events called after the user Opt-Out/Opt-In selection.<br />
*Test that the devId field is populated with a blank value if a user has elected to Opt-Out. For example: "devId": "",<br />
*If the device supports "Limit Ad Tracking" or has device "Opt-Out" settings, test that uoo=true, and that devId is set to a blank value if enabled in the device settings.<br />
<br />
== Go Live ==<br />
After your integration has been certified, you will need to: Change Endpoint and Disable Logging.<br />
<br />
'''Change Endpoint:''' You will need to update to the production endpoint:<br />
<br />
*Testing: <code>http://sandbox.cloudapi.imrworldwide.com/nmapi/v2/</code><br />
*Production: <code>https://cloudapi.imrworldwide.com/nmapi/v2/</code><br />
<br />
Your production URL structure should now be:<br />
<code>https://cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=[payload]</code><br />
<br />
'''Disable Logging:''' You can now disable debug logging</div>AlexGutierrezhttps://engineeringportal.nielsen.com//w/index.php?title=updateOTT_(Browser)&diff=2451updateOTT (Browser)2018-02-06T21:11:18Z<p>AlexGutierrez: </p>
<hr />
<div>{{Breadcrumb|}} {{Breadcrumb|Digital}} {{Breadcrumb|Browser SDK API Reference}} {{CurrentBreadcrumb}}<br />
[[Category:Digital]]<br />
[[Category:Browser SDK API Reference]]<br />
Use the ggPM function to pass <code>updateOTT</code> as event parameter in order to notify Browser SDK that the remote OTT device (like Google ChromeCast, Roku, Amazon FireTV, etc.) is connected / disconnected (change of OTT status).<br />
*When OTT device is connected, call <code>ggPM("updateOTT", {"ottStatus": "1",…})</code> and a set of OTT device related parameters in the same JSON object.<br />
*When OTT device is disconnected, call <code>ggPM("updateOTT",{"ottStatus": "0"}</code>).<br />
<br />
== Syntax ==<br />
<br />
<syntaxhighlight lang="javascript"><br />
ggPM("updateOTT", {"ottStatus": "1',…}); // Except VA users<br />
</syntaxhighlight><br />
<br />
== Input Parameters ==<br />
{| class="wikitable"<br />
|-<br />
! Parameter !! Description<br />
|-<br />
| ottInfo<br />
| A JSON string with the following parameters:<br />
'''OTT device is connected:'''<br />
<syntaxhighlight lang="json"><br />
{<br />
"ottStatus": "1",<br />
"ottType": "ottTypeStatus",<br />
"ottDevice": "ottchromecast",<br />
"ottDeviceName": ottDeviceNChromeCast",<br />
"ottDeviceID": "xxxx-xxxx-xxxx",<br />
"ottDeviceManufacturer": "ottDeviceMan",<br />
"ottDeviceModel": "ottChromeCast",<br />
"ottDeviceVersion": "1.0.0"<br />
}<br />
</syntaxhighlight><br />
<br />
<br />
'''OTT device is disconnected:'''<br />
<syntaxhighlight lang="json"><br />
{<br />
"ottStatus": "0"<br />
}<br />
</syntaxhighlight><br />
|}<br />
<br />
== Output Parameters ==<br />
{| class="wikitable"<br />
|-<br />
! Parameter !! Description<br />
|-<br />
| event || stop<br />
|-<br />
| playheadposition || The exact position of playhead when streaming was paused.<br />
|}<br />
<br />
== Output Parameters ==<br />
{| class="wikitable"<br />
|-<br />
! Output Parameters (Return value) !! Description<br />
|-<br />
| void ||<br />
|}<br />
<br />
== Notes ==<br />
* The player needs to report all the possible OTT types (casting, screen mirroring, etc.) to the Browser SDK.<br />
<br />
=== Communicating with the Chromecast Receiver App ===<br />
When acting as a sender device, Browser SDK cannot communicate directly with the Receiver App running on the Chromecast as it needs access to the Google Casting framework. Thus, Browser SDK requires the sender application to pass certain required data to the Receiver App.<br />
<br />
The application should:<br />
* Retrieve the Opt-Out status on the device (using getOptOutStatus()) and its Demographic ID (using getDemographicId())<br />
* Relay the retrieved details / values to the Receiver App, as additional parameters in MediaMetadata payload.<br />
* Create the metadata information for this purpose, using MediaMetadata.<br />
<br />
The two custom parameters to be included in MediaMetadata are:<br />
* kGCKMetadataNlsKeyDeviceID for device ID.<br />
* kGCKMetadataNlsKeyOptout for Opt-out status (true or false).<br />
<br />
Below is a sample code snippet on how the application should retrieve and relay the information from App SDK to Receiver App.<br />
<br />
<syntaxhighlight lang="javascript"><br />
MediaMetadata mediaMetadata = new MediaMetadata( MediaMetadata.MEDIA_TYPE_MOVIE );<br />
mediaMetadata.putString( MediaMetadata.KEY_TITLE, channelName.getText().toString());<br />
<br />
//Custom parameters<br />
mediaMetadata.putString( MEDIA_METADATA_KEY_DEVICE_ID, mAppSdk.getDemographicId() );<br />
mediaMetadata.putString( MEDIA_METADATA_KEY_OPTOUT_STATE, mAppSdk.getOptOutStatus());<br />
<br />
MediaInfo mediaInfo = new MediaInfo.Builder(mNowPlayingUrl)<br />
.setContentType( getString( R.string.content_type_mp4 ) )<br />
.setStreamType( MediaInfo.STREAM_TYPE_BUFFERED )<br />
.setMetadata( mediaMetadata )<br />
.build();<br />
</syntaxhighlight><br />
<br />
<br />
=== Receiver App Implementation ===<br />
When Browser SDK is run on a casting device acting as a receiver, the Browser SDK should get initialized on load of the receiver app. On loading the video player, content metadata should be passed using the loadMetadata event. Once loadMetadata has been called, the receiver app should send an updateOTT event to the Browser SDk receiver instance to relay the sender ottmetadata ( kGCKMetadataNlsKeyDeviceID and kGCKMetadataNlsKeyOptout) received from sender app.<br />
<br />
Below is a sample code snippet on how the receiver app should retrieve the ottMetadata received from sender apps and relay the information to receiver BSDK instance.<br />
<br />
<syntaxhighlight lang="javascript"><br />
sampleplayer.CastPlayer.prototype.onLoad_ = function(event) {<br />
<br />
var senderMetadata = event.data.media.metadata,<br />
<br />
sessionId =senderMetadata.kGCKMetadataNlsKeyDeviceID;<br />
<br />
this.cancelDeferredPlay_('new media is loaded');<br />
this.load(new cast.receiver.MediaManager.LoadInfo( (event.data), event.senderId));<br />
var contentMetadata = {<br />
"type": "content",<br />
"dataSrc": "cms",<br />
"assetid": "vid-123",<br />
"assetName": "Test video",<br />
"program": "Test program",<br />
"title": "Clickbaby",<br />
"length":”60”,<br />
"segA": "CustomSegmentValueA",<br />
"segB": "CustomSegmentValueB",<br />
"segC": "CustomSegmentValueC"<br />
};<br />
<br />
window.nolSDKInstance.ggPM(3, contentMetadata);<br />
<br />
var ottMetadataObject ={<br />
ottStatus: "1",<br />
ottType: "casting",<br />
ottDevice: "chromecast",<br />
ottDeviceName: "Google Chromecast",<br />
ottDeviceID: sessionId,<br />
ottDeviceManufacturer: "Google",<br />
ottDeviceModel: "ChromeCastModel",<br />
ottDeviceVersion: "1.0.0", <br />
kGCKMetadataNlsKeyOptout : senderMetadata.kGCKMetadataNlsKeyOptout,<br />
kGCKMetadataNlsKeyDeviceID : sessionId <br />
};<br />
<br />
window.nolSDKInstance.ggPM('updateOTT', ottMetadataObject);};<br />
</syntaxhighlight></div>AlexGutierrezhttps://engineeringportal.nielsen.com//w/index.php?title=updateOTT_(Browser)&diff=2450updateOTT (Browser)2018-02-05T17:11:48Z<p>AlexGutierrez: </p>
<hr />
<div>{{Breadcrumb|}} {{Breadcrumb|Digital}} {{Breadcrumb|Browser SDK API Reference}} {{CurrentBreadcrumb}}<br />
[[Category:Digital]]<br />
[[Category:Browser SDK API Reference]]<br />
Use the ggPM function to pass <code>updateOTT</code> as event parameter in order to notify Browser SDK that the remote OTT device (like Google ChromeCast, Roku, Amazon FireTV, etc.) is connected / disconnected (change of OTT status).<br />
*When OTT device is connected, call <code>ggPM("updateOTT", {"ottStatus": "1",…})</code> and a set of OTT device related parameters in the same JSON object.<br />
*When OTT device is disconnected, call <code>ggPM("updateOTT",{"ottStatus": "0"}</code>).<br />
<br />
== Syntax ==<br />
<br />
<syntaxhighlight lang="javascript"><br />
ggPM("updateOTT", {"ottStatus": "1',…}); // Except VA users<br />
</syntaxhighlight><br />
<br />
== Input Parameters ==<br />
{| class="wikitable"<br />
|-<br />
! Parameter !! Description<br />
|-<br />
| ottInfo<br />
| A JSON string with the following parameters:<br />
'''OTT device is connected:'''<br />
<syntaxhighlight lang="json"><br />
{<br />
"ottStatus": "1",<br />
"ottType": "ottTypeStatus",<br />
"ottDevice": "ottchromecast",<br />
"ottDeviceName": ottDeviceNChromeCast",<br />
"ottDeviceID": "xxxx-xxxx-xxxx",<br />
"ottDeviceManufacturer": "ottDeviceMan",<br />
"ottDeviceModel": "ottChromeCast",<br />
"ottDeviceVersion": "1.0.0"<br />
}<br />
</syntaxhighlight><br />
<br />
<br />
'''OTT device is disconnected:'''<br />
<syntaxhighlight lang="json"><br />
{<br />
"ottStatus": "0"<br />
}<br />
</syntaxhighlight><br />
|}<br />
<br />
== Output Parameters ==<br />
{| class="wikitable"<br />
|-<br />
! Parameter !! Description<br />
|-<br />
| event || stop<br />
|-<br />
| playheadposition || The exact position of playhead when streaming was paused.<br />
|}<br />
<br />
== Output Parameters ==<br />
{| class="wikitable"<br />
|-<br />
! Output Parameters (Return value) !! Description<br />
|-<br />
| void ||<br />
|}<br />
<br />
== Notes ==<br />
* The player needs to report all the possible OTT types (casting, screen mirroring, etc.) to the Browser SDK.<br />
* UpdateOTT should only be called from the Browser SDK if the Browser is acting as a sender device. <br />
* UpdateOTT should not be called if Browser SDK is running on a Chromecast as a receiver device.<br />
<br />
=== Communicating with the Chromecast Receiver App ===<br />
When acting as a sender device, Browser SDK cannot communicate directly with the Receiver App running on the Chromecast as it needs access to the Google Casting framework. Thus, Browser SDK requires the sender application to pass certain required data to the Receiver App.<br />
<br />
The application should:<br />
* Retrieve the Opt-Out status on the device (using getOptOutStatus()) and its Demographic ID (using getDemographicId())<br />
* Relay the retrieved details / values to the Receiver App, as additional parameters in MediaMetadata payload.<br />
* Create the metadata information for this purpose, using MediaMetadata.<br />
<br />
The two custom parameters to be included in MediaMetadata are:<br />
* kGCKMetadataNlsKeyDeviceID for device ID.<br />
* kGCKMetadataNlsKeyOptout for Opt-out status (true or false).<br />
<br />
Below is a sample code snippet on how the application should retrieve and relay the information from App SDK to Receiver App.<br />
<br />
<syntaxhighlight lang="javascript"><br />
MediaMetadata mediaMetadata = new MediaMetadata( MediaMetadata.MEDIA_TYPE_MOVIE );<br />
mediaMetadata.putString( MediaMetadata.KEY_TITLE, channelName.getText().toString());<br />
<br />
//Custom parameters<br />
mediaMetadata.putString( MEDIA_METADATA_KEY_DEVICE_ID, mAppSdk.getDemographicId() );<br />
mediaMetadata.putString( MEDIA_METADATA_KEY_OPTOUT_STATE, mAppSdk.getOptOutStatus());<br />
<br />
MediaInfo mediaInfo = new MediaInfo.Builder(mNowPlayingUrl)<br />
.setContentType( getString( R.string.content_type_mp4 ) )<br />
.setStreamType( MediaInfo.STREAM_TYPE_BUFFERED )<br />
.setMetadata( mediaMetadata )<br />
.build();<br />
</syntaxhighlight><br />
<br />
<br />
=== Receiver App Implementation ===<br />
When Browser SDK is run on a casting device acting as a receiver, the Browser SDK should get initialized on load of the receiver app. On loading the video player, content metadata should be passed using the loadMetadata event. Once loadMetadata has been called, the receiver app should send an updateOTT event to the Browser SDk receiver instance to relay the sender ottmetadata ( kGCKMetadataNlsKeyDeviceID and kGCKMetadataNlsKeyOptout) received from sender app.<br />
<br />
Below is a sample code snippet on how the receiver app should retrieve the ottMetadata received from sender apps and relay the information to receiver BSDK instance.<br />
<br />
<syntaxhighlight lang="javascript"><br />
sampleplayer.CastPlayer.prototype.onLoad_ = function(event) {<br />
<br />
var senderMetadata = event.data.media.metadata,<br />
<br />
sessionId =senderMetadata.kGCKMetadataNlsKeyDeviceID;<br />
<br />
this.cancelDeferredPlay_('new media is loaded');<br />
this.load(new cast.receiver.MediaManager.LoadInfo( (event.data), event.senderId));<br />
var contentMetadata = {<br />
"type": "content",<br />
"dataSrc": "cms",<br />
"assetid": "vid-123",<br />
"assetName": "Test video",<br />
"program": "Test program",<br />
"title": "Clickbaby",<br />
"length":”60”,<br />
"segA": "CustomSegmentValueA",<br />
"segB": "CustomSegmentValueB",<br />
"segC": "CustomSegmentValueC"<br />
};<br />
<br />
window.nolSDKInstance.ggPM(3, contentMetadata);<br />
<br />
var ottMetadataObject ={<br />
ottStatus: "1",<br />
ottType: "casting",<br />
ottDevice: "chromecast",<br />
ottDeviceName: "Google Chromecast",<br />
ottDeviceID: sessionId,<br />
ottDeviceManufacturer: "Google",<br />
ottDeviceModel: "ChromeCastModel",<br />
ottDeviceVersion: "1.0.0", <br />
kGCKMetadataNlsKeyOptout : senderMetadata.kGCKMetadataNlsKeyOptout,<br />
kGCKMetadataNlsKeyDeviceID : sessionId <br />
};<br />
<br />
window.nolSDKInstance.ggPM('updateOTT', ottMetadataObject);};<br />
</syntaxhighlight></div>AlexGutierrezhttps://engineeringportal.nielsen.com//w/index.php?title=updateOTT_(Browser)&diff=2449updateOTT (Browser)2018-02-05T17:11:15Z<p>AlexGutierrez: </p>
<hr />
<div>{{Breadcrumb|}} {{Breadcrumb|Digital}} {{Breadcrumb|Browser SDK API Reference}} {{CurrentBreadcrumb}}<br />
[[Category:Digital]]<br />
[[Category:Browser SDK API Reference]]<br />
Use the ggPM function to pass <code>updateOTT</code> as event parameter in order to notify Browser SDK that the remote OTT device (like Google ChromeCast, Roku, Amazon FireTV, etc.) is connected / disconnected (change of OTT status).<br />
*When OTT device is connected, call <code>ggPM("updateOTT", {"ottStatus": "1",…})</code> and a set of OTT device related parameters in the same JSON object.<br />
*When OTT device is disconnected, call <code>ggPM("updateOTT",{"ottStatus": "0"}</code>).<br />
<br />
== Syntax ==<br />
<br />
<syntaxhighlight lang="javascript"><br />
ggPM("updateOTT", {"ottStatus": "1',…}); // Except VA users<br />
</syntaxhighlight><br />
<br />
== Input Parameters ==<br />
{| class="wikitable"<br />
|-<br />
! Parameter !! Description<br />
|-<br />
| ottInfo<br />
| A JSON string with the following parameters:<br />
'''OTT device is connected:'''<br />
<syntaxhighlight lang="json"><br />
{<br />
"ottStatus": "1",<br />
"ottType": "ottTypeStatus",<br />
"ottDevice": "ottchromecast",<br />
"ottDeviceName": ottDeviceNChromeCast",<br />
"ottDeviceID": "xxxx-xxxx-xxxx",<br />
"ottDeviceManufacturer": "ottDeviceMan",<br />
"ottDeviceModel": "ottChromeCast",<br />
"ottDeviceVersion": "1.0.0"<br />
}<br />
</syntaxhighlight><br />
<br />
<br />
'''OTT device is disconnected:'''<br />
<syntaxhighlight lang="json"><br />
{<br />
"ottStatus": "0"<br />
}<br />
</syntaxhighlight><br />
|}<br />
<br />
== Output Parameters ==<br />
{| class="wikitable"<br />
|-<br />
! Parameter !! Description<br />
|-<br />
| event || stop<br />
|-<br />
| playheadposition || The exact position of playhead when streaming was paused.<br />
|}<br />
<br />
== Output Parameters ==<br />
{| class="wikitable"<br />
|-<br />
! Output Parameters (Return value) !! Description<br />
|-<br />
| void ||<br />
|}<br />
<br />
== Notes ==<br />
The player needs to report all the possible OTT types (casting, screen mirroring, etc.) to the Browser SDK.<br />
UpdateOTT should only be called from the Browser SDK if the Browser is acting as a sender device. UpdateOTT should not be called if Browser SDK is running on a Chromecast as a receiver device.<br />
<br />
=== Communicating with the Chromecast Receiver App ===<br />
When acting as a sender device, Browser SDK cannot communicate directly with the Receiver App running on the Chromecast as it needs access to the Google Casting framework. Thus, Browser SDK requires the sender application to pass certain required data to the Receiver App.<br />
<br />
The application should:<br />
* Retrieve the Opt-Out status on the device (using getOptOutStatus()) and its Demographic ID (using getDemographicId())<br />
* Relay the retrieved details / values to the Receiver App, as additional parameters in MediaMetadata payload.<br />
* Create the metadata information for this purpose, using MediaMetadata.<br />
<br />
The two custom parameters to be included in MediaMetadata are:<br />
* kGCKMetadataNlsKeyDeviceID for device ID.<br />
* kGCKMetadataNlsKeyOptout for Opt-out status (true or false).<br />
<br />
Below is a sample code snippet on how the application should retrieve and relay the information from App SDK to Receiver App.<br />
<br />
<syntaxhighlight lang="javascript"><br />
MediaMetadata mediaMetadata = new MediaMetadata( MediaMetadata.MEDIA_TYPE_MOVIE );<br />
mediaMetadata.putString( MediaMetadata.KEY_TITLE, channelName.getText().toString());<br />
<br />
//Custom parameters<br />
mediaMetadata.putString( MEDIA_METADATA_KEY_DEVICE_ID, mAppSdk.getDemographicId() );<br />
mediaMetadata.putString( MEDIA_METADATA_KEY_OPTOUT_STATE, mAppSdk.getOptOutStatus());<br />
<br />
MediaInfo mediaInfo = new MediaInfo.Builder(mNowPlayingUrl)<br />
.setContentType( getString( R.string.content_type_mp4 ) )<br />
.setStreamType( MediaInfo.STREAM_TYPE_BUFFERED )<br />
.setMetadata( mediaMetadata )<br />
.build();<br />
</syntaxhighlight><br />
<br />
<br />
=== Receiver App Implementation ===<br />
When Browser SDK is run on a casting device acting as a receiver, the Browser SDK should get initialized on load of the receiver app. On loading the video player, content metadata should be passed using the loadMetadata event. Once loadMetadata has been called, the receiver app should send an updateOTT event to the Browser SDk receiver instance to relay the sender ottmetadata ( kGCKMetadataNlsKeyDeviceID and kGCKMetadataNlsKeyOptout) received from sender app.<br />
<br />
Below is a sample code snippet on how the receiver app should retrieve the ottMetadata received from sender apps and relay the information to receiver BSDK instance.<br />
<br />
<syntaxhighlight lang="javascript"><br />
sampleplayer.CastPlayer.prototype.onLoad_ = function(event) {<br />
<br />
var senderMetadata = event.data.media.metadata,<br />
<br />
sessionId =senderMetadata.kGCKMetadataNlsKeyDeviceID;<br />
<br />
this.cancelDeferredPlay_('new media is loaded');<br />
this.load(new cast.receiver.MediaManager.LoadInfo( (event.data), event.senderId));<br />
var contentMetadata = {<br />
"type": "content",<br />
"dataSrc": "cms",<br />
"assetid": "vid-123",<br />
"assetName": "Test video",<br />
"program": "Test program",<br />
"title": "Clickbaby",<br />
"length":”60”,<br />
"segA": "CustomSegmentValueA",<br />
"segB": "CustomSegmentValueB",<br />
"segC": "CustomSegmentValueC"<br />
};<br />
<br />
window.nolSDKInstance.ggPM(3, contentMetadata);<br />
<br />
var ottMetadataObject ={<br />
ottStatus: "1",<br />
ottType: "casting",<br />
ottDevice: "chromecast",<br />
ottDeviceName: "Google Chromecast",<br />
ottDeviceID: sessionId,<br />
ottDeviceManufacturer: "Google",<br />
ottDeviceModel: "ChromeCastModel",<br />
ottDeviceVersion: "1.0.0", <br />
kGCKMetadataNlsKeyOptout : senderMetadata.kGCKMetadataNlsKeyOptout,<br />
kGCKMetadataNlsKeyDeviceID : sessionId <br />
};<br />
<br />
window.nolSDKInstance.ggPM('updateOTT', ottMetadataObject);};<br />
</syntaxhighlight></div>AlexGutierrezhttps://engineeringportal.nielsen.com//w/index.php?title=DCR_OTT_Genre_List&diff=2436DCR OTT Genre List2018-01-29T21:24:46Z<p>AlexGutierrez: </p>
<hr />
<div>{{Breadcrumb|}} {{Breadcrumb|Digital}} {{Breadcrumb|DCR & DTVR}} {{CurrentBreadcrumb}}<br />
[[Category:Digital]]<br />
<br />
The OTT Genre list is used to populate the <code>progen</code> parameter only for OTT measurement. The Genre parameter should only be populated for video content that is not TV-originated.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Category !! Genre Code<br />
|-<br />
| Adventure || A<br />
|-<br />
| Audience Participation || AP<br />
|-<br />
| Award Ceremonies & Pageants || AC<br />
|-<br />
| Children’s Programming || CP<br />
|-<br />
| Comedy Variety || CV<br />
|-<br />
| Concert Music || CM<br />
|-<br />
| Conversation, Colloquies || CC<br />
|-<br />
| Daytime Drama || DD<br />
|-<br />
| Devotional || D<br />
|-<br />
| Documentary, General || DO<br />
|-<br />
| Documentary, News || DN<br />
|-<br />
| Evening Animation || EA<br />
|-<br />
| Feature Film || FF<br />
|-<br />
| General Drama || GD<br />
|-<br />
| General Variety || GV<br />
|-<br />
| Instructions, Advice || IA<br />
|-<br />
| Musical Drama || MD<br />
|-<br />
| News || N<br />
|-<br />
| Official Police || OP<br />
|-<br />
| Paid Political || P <br />
|-<br />
| Participation Variety || PV<br />
|-<br />
| Popular Music || PM<br />
|-<br />
| Private Detective || PD<br />
|-<br />
| Quiz -Give Away || QG<br />
|-<br />
| Quiz -Panel || QP <br />
|-<br />
| Science Fiction || SF <br />
|-<br />
| Situation Comedy || CS<br />
|-<br />
| Sports Anthology || SA<br />
|-<br />
| Sports Commentary || SC <br />
|-<br />
| Sports Event || SE <br />
|-<br />
| Sports News || SN <br />
|-<br />
| Suspense/Mystery || SM<br />
|-<br />
| Western Drama || EW<br />
|}</div>AlexGutierrezhttps://engineeringportal.nielsen.com//w/index.php?title=DCR_Video_%26_Static_Cloud_API&diff=2435DCR Video & Static Cloud API2018-01-29T21:23:34Z<p>AlexGutierrez: </p>
<hr />
<div>{{Breadcrumb|}} {{Breadcrumb|Digital}} {{Breadcrumb|DCR & DTVR}} {{CurrentBreadcrumb}}<br />
[[Category:Digital]]<br />
<br />
This guide shows you how to integrate the Nielsen Cloud API to enable Digital Content Ratings (DCR) measurement on your over-the-top (OTT) Apps.<br />
*For Roku Apps, please see [[DCR Video & Static Roku Cloud API]]<br />
*For Mobile Apps, please see [[DCR Video & Static Mobile Cloud API]]<br />
<br />
==Prerequisites==<br />
To get started, you will need a Nielsen App ID. The App ID is a unique ID assigned to your app. This will be provided to you upon starting the integration.<br />
<br />
<syntaxhighlight lang="javascript">XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX</syntaxhighlight><br />
<br />
==Integration==<br />
We will cover the steps for constructing the Cloud API Calls.<br />
<br />
===Request Overview===<br />
<br />
====URL Structure====<br />
<br />
The Cloud API Calls are HTTP GET Requests with the URL structure:<br />
<br />
<syntaxhighlight lang="javascript">[endpoint]/[appid]/[sessionID]/a?b=[payload]</syntaxhighlight><br />
<br />
The URL includes the following components:<br />
<br />
*<code>[endpoint]</code>: location of data collection environment<br />
*<code>[appid]</code>: provided App ID<br />
*<code>[sessionID]</code>: unique value for each user session<br />
*<code>[payload]</code>: metadata and events<br />
<br />
====Endpoint====<br />
<br />
There are endpoints for testing and production:<br />
<br />
*Testing: <code>http://sandbox.cloudapi.imrworldwide.com/nmapi/v2/</code><br />
*Production: <code>https://cloudapi.imrworldwide.com/nmapi/v2/</code><br />
<br />
During testing, all calls should be pointed to the testing endpoint. We will review the update to the production endpoint during the Go Live section of this guide.<br />
<br />
====URL Example====<br />
As you move through the integration steps, you can reference the below URL structure with the expanded payload:<br />
<br />
<syntaxhighlight lang="javascript"><br />
http://sandbox.cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=<br />
{<br />
"devInfo": [deviceInfo],<br />
"metadata": {<br />
"static": [static_metadata],<br />
"content": [content_metadata],<br />
"ad": [ad metadata]<br />
},<br />
"event": [event],<br />
"position": [playhead_position],<br />
"type": [asset type],<br />
"utc": [UTC]<br />
}<br />
</syntaxhighlight><br />
<br />
===Create Session ID===<br />
A unique Session ID must be created upon app launch and provided in the URL. This will allow measurement to occur for the entire duration that a user is within the app.<br />
<br />
The session ID must be a GUID that is passed with every request and remain consistent throughout each individual session. You can use an existing ID, a random number, or date.now() as shown in the recommended sample code below:<br />
<br />
<syntaxhighlight lang="javascript">sessionID = Date.now()+String(Math.random()*1000000 >> 0);</syntaxhighlight><br />
<br />
Upon exiting the app, the session will need to be terminated using the delete event. Sessions will automatically expire after 30 minutes of cloud inactivity.<br />
<br />
===Define URL Structure===<br />
Define the URL structure using your provided <code>[appid]</code> and a unique <code>[sessionID]</code>.<br />
<br />
<syntaxhighlight lang="javascript">http://sandbox.cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=[payload]</syntaxhighlight><br />
<br />
===Configure Payload===<br />
<br />
All Cloud API requests must contain the following payload data:<br />
<br />
*''devInfo'': device and app info<br />
*''metadata'': asset metadata<br />
*''event metadata'': type of event<br />
<br />
The payload can be passed through key-values using the Nielsen reserved keys. The specific keys and descriptions are highlighted in the tables included in this section.<br />
<br />
'''Payload Example'''<br />
<br />
The example below should be referenced when following the steps for configuring the request payload.<br />
<br />
<syntaxhighlight lang="javascript"><br />
// 3.1 Configure Payload: devInfo <br />
payload = {<br />
"devInfo": {<br />
"devId": "AD-ID", <br />
"apn": "AppName",<br />
"apv": "1.0",<br />
"uoo": "false"<br />
},<br />
<br />
// 3.2 Configure Payload: metadata<br />
"metadata": {<br />
"static": {}, // object for measuring static content<br />
"content": { // object for measuring video content<br />
"type": "content", // "content" for video<br />
"assetid": "VIDEO-ID123", // unique ID for video<br />
"isfullepisode": "y", // full episode flag<br />
"program": "Program Name", // program name<br />
"title": "Episode Title S3 - EP1", // episode name<br />
"length": "1800", // content duration in seconds<br />
"segB": "Custom Segment B", // custom segment<br />
"segC": "Custom Segment C", // custom segment<br />
"crossId1": "Standard Episode ID", // episode ID<br />
"crossId2": "Content Originator ID", // content orginator (required for distributors)<br />
"airdate": "20161013 20:00:00", // airdate<br />
"adloadtype": "2" //ad load flag<br />
"hasAds": "1", // content contains ads = 1 / no ads = 0<br />
"progen": "CV" // program genre abbreviation<br />
},<br />
"ad": {<br />
"type": "preroll", // type of ad<br />
"assetid": "AD-ID123" // unique ID for ad<br />
}<br />
},<br />
<br />
// 3.3 Configure Payload: events<br />
"event": "playhead", //event name<br />
"position": "300", // position in seconds<br />
"type": "content", //"content" or "ad"<br />
"utc": "1456448742000" //unix timestamp in milliseconds <br />
}<br />
</syntaxhighlight><br />
<br />
=====Configure Payload: devInfo=====<br />
An object <code>"devInfo"</code> will need to be created to capture App and Device information.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| devId || unique ID to identify user (e.g. Advertising ID, Roku Device ID) || custom || Yes<br />
|-<br />
| apn || app name || custom || Yes<br />
|-<br />
| apv || app build version || custom || Yes<br />
|-<br />
| uoo || device opt-out status || <code>"true"</code> or <code>"false"</code> || Yes<br />
|-<br />
|}<br />
<br />
'''Example devInfo Object'''<br />
<syntaxhighlight lang="javascript"><br />
// create devInfo object<br />
"devInfo": {<br />
"devId": "AD-ID",<br />
"apn": "AppName",<br />
"apv": "1.0",<br />
"uoo": "false"<br />
},<br />
</syntaxhighlight><br />
<br />
==== 3.2 Configure Payload: metadata ====<br />
Asset metadata can be passed through <code>"metadata"</code>. There are two asset types: <code>"content"</code> for video and <code>"ad"</code> for ads. The metadata received for each asset is used for classification and reporting.<br />
<br />
You will need to set up <code>"metadata"</code> objects for <code>"content"</code> and <code>"ad"</code> with the required Nielsen keys as shown in the sample code below.<br />
<br />
===== Content Metadata =====<br />
Content metadata should remain constant throughout the entirety of an episode/clip including when ads play.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| type || type of asset || <code>"content"</code> || Yes<br />
|-<br />
| assetid || unique ID assigned to asset || custom || Yes<br />
|-<br />
| program ||name of program (25 character limit) || custom || Yes<br />
|-<br />
| title ||name of program (25 character limit) || custom || Yes<br />
|-<br />
| length || length of content in seconds || <code>seconds</code> (86400 for live stream) || Yes<br />
|-<br />
| segB || custom segment B || custom || <br />
|-<br />
| segC || custom segment C || custom || <br />
|-<br />
| airdate || the airdate in the linear TV || YYYYMMDD HH24:MI:SS || Yes<br />
|-<br />
| isfullepisode || full episode flag || <code>"y"</code>- full episode, <code>"n"</code>- non full episode || Yes<br />
|-<br />
| crossId1 || standard episode ID || custom || Yes<br />
|-<br />
| crossId2 || content originator (only required for distributors) || Nielsen ||<br />
|-<br />
| adloadtype || type of ad load:<br />
<code>"1"</code> Linear – matches TV ad load<br />
<br />
<code>"2"</code> Dynamic – Dynamic Ad Insertion (DAI)<br />
|| <code>"2"</code> - DCR measures content with dynamic ads || Yes<br />
|-<br />
| hasAds || ads indicator<br />
<code>"1"</code>: ads included<br />
<br />
<code>"0"</code>: ads not included<br />
|| <code>"1"</code> or <code>"0"</code> || Yes<br />
|-<br />
| progen || Genre (required only for non-TV originated content). See [[DCR OTT Genre List]] for accepted values || <code>"CV"</code> for Comedy Variety || Required for non-TV originated content<br />
|}<br />
<br />
<br />
'''Example Content Object'''<br />
<syntaxhighlight lang='json'>// create content object<br />
"content": {<br />
"type": "content",<br />
"assetid": "VIDEO-ID123",<br />
"isfullepisode": "y",<br />
"program": "Program Name",<br />
"title": "Episode Title S3 - EP1",<br />
"length": "1800",<br />
"segB": "Custom Segment B",<br />
"segC": "Custom Segment C",<br />
"crossId1": "Standard Episode ID",<br />
"crossId2": "Content Originator ID",<br />
"airdate": "20161013 20:00:00",<br />
"adloadtype": "2",<br />
"hasAds": "1", <br />
"progen": "CV"<br />
}</syntaxhighlight><br />
<br />
===== Ad Metadata =====<br />
The ad metadata should be passed for each individual ad.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| type || type of ad || <code>"preroll"</code>, <code>"midroll"</code>, or <code>"postroll"</code> || Yes<br />
|-<br />
| assetid || unique ID assigned to ad || custom || Yes<br />
|}<br />
<br />
===== Example Ad Object =====<br />
<syntaxhighlight lang="javascript"><br />
// create ad object<br />
"ad": {<br />
"type": "preroll",<br />
"assetid": "AD-ID123"<br />
}<br />
</syntaxhighlight><br />
<br />
=== Configure Payload: Events ===<br />
<br />
The last part of the payload is for enabling events so content is measured correctly when viewed. The events and required parameters are included below.<br />
<br />
==== Event Types ====<br />
<br />
The available events are:<br />
{| class="wikitable"<br />
|-<br />
! Event !! Description<br />
|-<br />
| <code>"playhead"</code> || Playhead position in seconds. Must be passed as a whole number every 10 seconds. The final playhead position should also be sent before an asset has changed to properly capture full duration. Playhead is used to handle pause and scrubbing. When content is paused, stop passing playhead position.<br />
|-<br />
| <code>"complete"</code> || Complete event must be sent when the content has completed full playback or if a user has initiated a Stop. Before calling the complete event, a final playhead update with the final position is required to be sent to receive full duration credit. Complete does not need to be called if a user transitions videos (e.g. channel change or selecting a new VOD asset) before the video completes.<br />
|-<br />
| <code>"delete"</code> || The delete event should be sent when the application is exited and the viewing session is completed. After 30 minutes of inactivity, the session will expire automatically. All creditable duration will be summarized for all asset types when delete occurs (content and ads). Note that the delete event may not be feasible on certain platforms that do not offer the necessary callbacks to trigger this event.<br />
|}<br />
<br />
===== Event Parameters =====<br />
<br />
The following parameters need to be passed when calling events:<br />
<br />
{| class="wikitable"<br />
|-<br />
! Parameter !! Description !! Value !! Required<br />
|-<br />
| <code>"event"</code> || event type || <code>"playhead"</code>, <code>"complete"</code>, or <code>"delete"</code> || Yes<br />
|-<br />
| <code>"position"</code> || creditable position || playhead position in seconds or UTC timestamp in seconds for livestream || Yes<br />
|-<br />
| <code>"type"</code> || asset type || <code>"content"</code>, <code>"ad"</code> || Yes<br />
|-<br />
| <code>"utc"</code> || Unix timestamp in milliseconds. Must be passed every 10 seconds. || <code>"1472760000000"</code> || Yes<br />
|}<br />
<br />
===== Example Event =====<br />
You can call events by passing values in the required parameters:<br />
<br />
<syntaxhighlight lang="javascript"><br />
"devInfo": [deviceInfo],<br />
"metadata": {<br />
"static": [static metadata],<br />
"content": [content metadata],<br />
"ad": [ad metadata]<br />
},<br />
// Event Parameters<br />
"event": [event], // event name<br />
"position": [playheadPosition], //position in seconds<br />
"type": [asset type], // values are "content" or "ad"<br />
"utc": "1472760000000" //unix timestamp in milliseconds<br />
}<br />
</syntaxhighlight><br />
<br />
'''Note:''' The full payload including "devInfo" and "metadata" must be populated in each event request.<br />
<br />
===== Sample Event Lifecycle =====<br />
The sample event lifecycle can be used as a reference for identifying the order for calling events and values to pass.<br />
<br />
<syntaxhighlight lang="javascript"><br />
// Start of Session: session ID created when App is opened<br />
<br />
// Preroll<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
// Content<br />
Content Playhead {"event": "playhead", "position": "0", "type": "content", "utc": "1472760000000"} <br />
<br />
// Midroll<br />
Midroll Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
// Content resumes at 15 minutes<br />
Content Playhead {"event": "playhead", "position": "900", "type": "content", "utc": "1472760000000"} <br />
<br />
// Content completes at 30 minutes<br />
Complete {"event": "complete", "position": "1800", "type": "content", "utc": "1472760000000"} <br />
<br />
// Postroll<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
//End of Session: The delete event should be called when the App is exited. The values for position and type not required to be passed.<br />
Delete { "event": "delete", "position": "", "type": "", "utc": "1472760000000"} <br />
</syntaxhighlight><br />
<br />
<br />
'''Sample Event Lifecycle - Detailed Storyline'''<br />
This detailed event sequence provides additional insight for the correct events to call when handling certain playback scenarios.<br />
<syntaxhighlight lang='javascript'>// SESSION STARTS<br />
// Start of Session: session ID created when App is opened<br />
<br />
// PREROLL<br />
// Preroll Start - Start each Ad with a position of "0", resetting to '0' for each Ad, and Ad break.<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
// Preroll Stop - End each Ad with the final position of the Ad.<br />
Ad Playhead {"event": "playhead", "position": "15", "type": "ad", "utc": "1472761500000"}<br />
<br />
// CONTENT <br />
// Content Start - Start new content streams with a position of "0" incrementing the position every 10 seconds.<br />
Content Playhead {"event": "playhead", "position": "0", "type": "content", "utc": "1472761500000"} <br />
<br />
// Content Stop Before Ad Break - Send a playhead update including the current content positon before an Ad break.<br />
Content Playhead {"event": "playhead", "position": "299", "type": "content", "utc": "1472787400000"}<br />
<br />
// MIDROLL<br />
// Midroll Start - Start each Ad with a position of "0", resetting to '0' for each Ad, and Ad break.<br />
Midroll Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472787500000"} <br />
<br />
// Midroll Stop - End each Ad with the final position of the Ad.<br />
Ad Playhead {"event": "playhead", "position": "60", "type": "ad", "utc": "1472793500000"}<br />
<br />
// CONTENT<br />
// Content resumes at 5 minutes - Send playhead update with the current resumed position, and begin incrimenting the positon every 10 seconds.<br />
Content Playhead {"event": "playhead", "position": "300", "type": "content", "utc": "1472799500000"} <br />
<br />
// Content completes at 10:12 - Make sure to send in the playhead event with the final content position before sending the complete event.<br />
Final Content Playhead {"event": "playhead", "position": "612", "type": "content", "utc": "1472830700000"} <br />
<br />
Complete {"event": "complete", "position": "612", "type": "content", "utc": "1472830800000"} <br />
<br />
// POSTROLL<br />
// Postroll Start - Start each Ad with a position of "0", resetting to '0' for each Ad, and Ad break.<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472830900000"} <br />
<br />
// Postroll Stop - End each Ad with the final position of the Ad.<br />
Ad Playhead {"event": "playhead", "position": "45", "type": "ad", "utc": "1472835300000"}<br />
<br />
// SESSION ENDS<br />
<br />
//End of Session: The delete event should be called when the App is exited. The values for position and type not required to be passed.<br />
Delete { "event": "delete", "position": "", "type": "", "utc": "1472835400000"} </syntaxhighlight><br />
<br />
<br />
=====Handling Playhead=====<br />
Calling <code>"playhead"</code> is critical for accurate duration crediting. You can reference the below guidance to determine the correct playhead position to pass depending on the playback scenario.<br />
<br />
'''Playhead: General'''<br />
* Playhead position must start at 0 for each new asset, and be passed at least every 10 seconds.<br />
* Final postion must be sent at the end of content or an ad<br />
<br />
'''Playhead: Ads'''<br />
* The final position must be sent when switching from content to ad, or ad to content.<br />
* Each ad playhead position should be 0 at ad start.<br />
* For Ad Pods, playhead must be called, and reset to 0 for each individual ad. <br />
* The last content position before an Ad should be sent before switching to Ads.<br />
* When content has resumed following an ad break, the playhead position update must continue where the previous content segment left off.<br />
<br />
'''Playhead: User Actions'''<br />
* Upon user scrubbing, the current position must be sent before a user scrubs, and the new position should be sent where the user lands, and begin sending in the 10 second updates thereafter.<br />
* On pause, send the current position and then discontinue sending playhead event updates.<br />
* If a user exits a stream early, the last current position must be sent in a playhead update to receive accurate duration.<br />
<br />
===== Interruption Scenarios =====<br />
<br />
As part of configuring events, you will need to handle all possible interruption scenarios such as:<br />
<br />
*Wi-Fi OFF / ON<br />
*App going Background / Foreground (Video players only, not for Audio players)<br />
*App Crash or Exit<br />
<br />
When playback is interrupted, the app needs to send delete immediately.<br />
<br />
Once playback resumes, a new session will need to be created with a unique session ID. All of the required metadata and events will need to be sent.<br />
<br />
'''Note:''' The session will automatically timeout after 30 minutes of inactivity.<br />
<br />
=== Example Request ===<br />
<br />
Now that we walked through the Cloud API integration steps, your requests should have the following components: Session ID, App ID, and Payload. You can reference the example below when your reviewing your integration.<br />
<br />
<syntaxhighlight lang="javascript"><br />
// 1. Create Session ID<br />
sessionID = Date.now()+String(Math.random()*1000000 >> 0); // Must be GUID for each viewing session<br />
<br />
// 2. Define URL Structure with App ID and Session ID<br />
sessionURL = http://sandbox.cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=<br />
<br />
// 3. Configure Payload<br />
// 3.1 Configure Payload: devInfo <br />
payload = {<br />
"devInfo": {<br />
"devId": "AD-ID", <br />
"apn": "AppName",<br />
"apv": "1.0",<br />
"uoo": "false"<br />
},<br />
<br />
// 3.2 Configure Payload: metadata<br />
"metadata": {<br />
"static": {}, // object for measuring static content<br />
"content": { // object for measuring video content<br />
"type": "content", // "content" for video<br />
"assetid": "VIDEO-ID123", // unique ID for video<br />
"isfullepisode": "y", // full episode flag<br />
"program": "Program Name", // program name<br />
"title": "Episode Title S3 - EP1", // episode name<br />
"length": "1800", // content duration in seconds<br />
"segB": "Custom Segment B", // custom segment<br />
"segC": "Custom Segment C", // custom segment<br />
"crossId1": "Standard Episode ID", // episode ID<br />
"crossId2": "Content Originator ID", // content orginator (required for distributors)<br />
"airdate": "20161013 20:00:00", // airdate<br />
"adloadtype": "2", // ad load flag<br />
"hasAds": "1", // content contains ads = 1 / no ads = 0<br />
"progen": "CV" // program genre abbreviation<br />
},<br />
"ad": {<br />
"type": "preroll", // type of ad<br />
"assetid": "AD-ID123" // unique ID for ad<br />
}<br />
},<br />
<br />
// 3.3 Configure Payload: events<br />
"event": "playhead", //event name<br />
"position": "300", // position in seconds<br />
"type": "content", //"content" or "ad"<br />
"utc": "1456448742000" //unix timestamp in milliseconds <br />
}<br />
<br />
// Append payload to URL<br />
var image = new Image()<br />
image.onerror = function() {<br />
// wait and send again<br />
}<br />
(new Image).src = sessionURL+encodeURI(JSON.stringify(payload));<br />
</syntaxhighlight><br />
<br />
==== Enable Debug Logging ====<br />
<br />
Now that you have set up the Cloud API requests, you can enable debug logging to validate your integration. Enabling debug logging is required for Nielsen certification.<br />
<br />
==== GET Request ====<br />
<br />
Display GET Request to console using a name to identify each event (e.g. playhead).<br />
<br />
<syntaxhighlight lang="javascript"><br />
console.log("Event", image); <br />
</syntaxhighlight><br />
<br />
==== Payload ====<br />
<br />
Output payload to identify required metadata and events.<br />
<br />
<syntaxhighlight lang="javascript"><br />
console.log("Event Payload", payload); <br />
</syntaxhighlight><br />
<br />
==== HTTP Response Code ====<br />
<br />
Confirm request was completed by viewing HTTP response code.<br />
<br />
<syntaxhighlight lang="javascript"><br />
code = msg.GetResponseCode();<br />
console.log("Response Code", code); <br />
</syntaxhighlight><br />
<br />
You can reference the HTTP Response Code table when reviewing your requests:<br />
<br />
{| class="wikitable"<br />
|-<br />
! Status Code !! Status Text !! Description<br />
|-<br />
| <code>200</code> || OK || request received<br />
|-<br />
| <code>403</code> || Forbidden || invalid App ID<br />
|-<br />
| <code>404</code> || Not Found || JSON issue<br />
|}<br />
<br />
== Opt-Out ==<br />
Your app must provide a means for the user to Opt-Out, or Opt-In to Nielsen Measurement. The Opt-Out requirement can be fulfilled by creating an Opt-Out/Opt-In button, toggle switch, or slider within the app "Settings", or "About" section to allow the user selection. If you have the ability to render text within the Opt-Out screen, it is recommended to add the Privacy Information included below.<br />
<br />
[[File:Nielsen Opt-Out.png|link=]]<br />
<br />
You will need to store the User Opt-Out (uoo) status, so that it can be retrieved and populated in the <code>"devInfo"</code> metadata which will be sent in every Cloud API event (playhead, complete, & delete). <br />
<br />
*In addition, you will need to ensure that the devId field is a blank value if a user has elected to Opt-Out.<br />
<br />
*If the device offers "Limit Ad Tracking" or "Opt-Out" device settings, you must set uoo=true, and also ensure that the devId is set to a blank value if the user elects to enable this setting.<br />
<br />
{| class="wikitable"<br />
|-<br />
! uoo Key !! Description !! Values<br />
|-<br />
| uoo || Device is Opted-In to Nielsen Measurement (Recommended Default Setting) || <code>"false"</code><br />
|-<br />
| uoo || Device is Opted-Out of Nielsen Measurement || <code>"true"</code><br />
|}<br />
<br />
===== devInfo Opt-In JSON Payload Example =====<br />
<br />
<syntaxhighlight lang="javascript"><br />
"devInfo": {<br />
"apn": "Roku Sample App",<br />
"apv": "1",<br />
"devId": "7be25cf9-8c40-5cc2-871e-19bf41940288",<br />
"uoo": "false"<br />
},<br />
</syntaxhighlight><br />
<br />
===== devInfo Opt-Out JSON Payload Example =====<br />
<br />
<syntaxhighlight lang="javascript"><br />
"devInfo": {<br />
"apn": "Roku Sample App",<br />
"apv": "1",<br />
"devId": "", //devId must be blank when a user elects to Opt-Out.<br />
"uoo": "true"<br />
},<br />
</syntaxhighlight><br />
<br />
===== Privacy Information Template To Include In Opt-Out Screen =====<br />
<br />
<blockquote><br />
'''''ABOUT NIELSEN MEASUREMENT'''''<br />
<br />
Television and the way we watch it have come a long way since Nielsen began measuring TV audiences in 1950. Today, the ability to watch our favorite shows at any time and on multiple devices amplifies the need for exceptionally adept and flexible audience measurement capabilities.<br />
<br />
Consumers are changing with the times, and the same goes for us. As technology continues to evolve and media companies try new ways to attract viewers, understanding what consumers are watching — and what they're watching on — is more important than ever. Today, viewing video is a personal and mobile experience — anytime and anywhere. Our capabilities provide relevant metrics that are necessary to inform successful marketing and programming and drive continued growth. As a global information and measurement leader, we are committed to protecting the privacy and security of the data we collect, process and use. While our digital measurement products are not used to identify you in any way, they help us and our clients measure and analyze how consumers engage with media across online, mobile and emerging technologies, and offer insights into consumer behavior.<br />
<br />
'''''YOUR CHOICES'''''<br />
<br />
Nielsen believes that you should have a choice about whether to contribute to our research and insights. To opt out, or opt into Nielsen measurement please toggle your "Limit Ad Tracking" (or similar setting) on your device. If you have this app on more than one device, you will need to opt out of this app on each device. To learn more about our digital measurement products and your choices in regard to them, please visit http://www.nielsen.com/digitalprivacy.<br />
<br />
'''''Disclosure Template'''''<br />
<br />
Additionally, you must update the App description information from the App Distribution Store with the Nielsen Measurement disclosure below:<br />
<br />
This app features Nielsen's proprietary measurement software which will allow you to contribute to market research, like Nielsen's TV Ratings. Please see http://www.nielsen.com/digitalprivacy for more information.<br />
</blockquote><br />
<br />
== Testing ==<br />
Before providing an app build to Nielsen for testing, it is important to run validation checks once you have enabled debug logging.<br />
<br />
=== Payload Validation ===<br />
<br />
Ensure that all of the required payload data is populating while testing several videos. The following areas are critical to measurement:<br />
*devInfo<br />
*Asset metadata for both content, and ads<br />
*Events<br />
*Opt-Out status<br />
<br />
=== Player Events ===<br />
Review event calls:<br />
<br />
==== playhead ====<br />
*Playhead position updates every 10 seconds starting at position '0' for each new asset.<br />
*Final playhead position is sent on content, or ad before switching between assets.<br />
*Content metadata remains constant throughout an episode, or clip play.<br />
*Ad metadata is populated appropriately for each individual ad.<br />
*Playhead position update resumes for content after an ad break, and resets to 0 for each individual ad.<br />
*For scrubbing, last current position should be sent while scrubbing occurs, and the new position should also be sent where the user scrubs to.<br />
*Exiting a stream early should execute the last current position in a playhead update to receive accurate duration.<br />
*Upon pause, the current position should be sent, and playhead updates should stop incrementing until resume play occurs.<br />
<br />
==== complete ====<br />
*Check that the complete event executes upon content complete after the final playhead update is sent<br />
*Do not execute the complete event for ads<br />
<br />
==== delete ====<br />
*Check to see that the delete event occurs upon app exit, if the platform has the necessary exit callback events.<br />
<br />
==== GET Request Format ====<br />
*Ensure that the event payloads are formatted in JSON<br />
*Check to see that each of the Cloud API GET requests are properly encoded<br />
<br />
==== HTTP Response ====<br />
*Make sure that each of the Cloud API Get requests are received by the Nielsen Cloud API properly through use of the HTTP Response Code outputs enabled in console.<br />
<br />
==== Opt-Out ====<br />
*Test the "uoo" key gets populated accurately for both Opt-In and Opt-Out selections by validating the Cloud API events called after the user Opt-Out/Opt-In selection.<br />
*Test that the devId field is populated with a blank value if a user has elected to Opt-Out. For example: "devId": "",<br />
*If the device supports "Limit Ad Tracking" or has device "Opt-Out" settings, test that uoo=true, and that devId is set to a blank value if enabled in the device settings.<br />
<br />
== Go Live ==<br />
After your integration has been certified, you will need to: Change Endpoint and Disable Logging.<br />
<br />
'''Change Endpoint:''' You will need to update to the production endpoint:<br />
<br />
*Testing: <code>http://sandbox.cloudapi.imrworldwide.com/nmapi/v2/</code><br />
*Production: <code>https://cloudapi.imrworldwide.com/nmapi/v2/</code><br />
<br />
Your production URL structure should now be:<br />
<code>https://cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=[payload]</code><br />
<br />
'''Disable Logging:''' You can now disable debug logging</div>AlexGutierrezhttps://engineeringportal.nielsen.com//w/index.php?title=Digital_Measurement_Metadata&diff=2431Digital Measurement Metadata2018-01-29T17:55:02Z<p>AlexGutierrez: </p>
<hr />
<div>{{Breadcrumb|}} {{Breadcrumb|Digital}} {{Breadcrumb|DCR & DTVR}} {{CurrentBreadcrumb}}<br />
[[Category:Digital]]<br />
<br />
The metadata received for each asset is used for classification and reporting. There are reserved Nielsen keys for collecting the required metadata.<br />
<br />
=== Reserved Keys ===<br />
Content and Ad Metadata can be passed through key-values using the Nielsen reserved keys. The last column in the table below indicates metadata parameters that will be displayed in reporting.<br />
<br />
==== Content Metadata ====<br />
{| class="wikitable"<br />
|-<br />
! Key<br />
! Description<br />
! style="width: 14%;" | Values<br />
! style="width: 12%;" | Required for Audio/Video?<br />
! style="width: 12%;" | Required for Static (page)?<br />
! Reported for<br />
|-<br />
| clientid || Brand value is automatically populated through the App ID provided. The value passed here will override the default value. (e.g. Multiple brands in App) || custom || Optional || Optional || Audio / Video and Static Measurement<br />
|-<br />
| vcid || VCID value is automatically populated through the App ID provided. This value passed here will override the default value. (e.g. Multiple Sub-brands in App) || custom || Optional || Optional || Audio / Video and Static Measurement<br />
|-<br />
| type ||<br />
Type of measurement:<br />
*audio / video – "content"<br />
*page – "static"<br />
*ad – "preroll", "midroll" or "postroll"<br />
|| "content", "static" || Mandatory || Mandatory || <br />
|-<br />
| assetid || ID assigned to content. Must be unique across all content at the episode level. || custom || Mandatory || Not Required || <br />
|-<br />
| section || Section of site || custom || Not Required || Mandatory || <br />
|-<br />
| isfullepisode || Full episode flag ||<br />
*"y" or "lf" - full episode<br />
*"n" or "sf" - short form<br />
|| Mandatory || Not Required ||<br />
|-<br />
| program || Program name || custom || Mandatory || Not Required || Audio / Video Measurement<br />
|-<br />
| title || Episode title || custom || Mandatory || Not Required || Audio / Video Measurement<br />
|-<br />
| length || Length of content in seconds for VOD. 86400 may be used for Live content if the exact content length is not known. || custom || Mandatory || Not Required ||<br />
|-<br />
| segA || Segment A (this is not available for audio / video Measurement reporting as the episode title is reported) || custom || Not Required || Not Required || Static Measurement<br />
|-<br />
| segB || Segment B || custom || Not Required || Not Required || Audio / Video and Static Measurement<br />
|-<br />
| segC || Segment C || custom || Not Required || Not Required || Audio / Video and Static Measurement<br />
|-<br />
| crossId1 || Standard episode ID. Gracenote/TMS ID should be used when available. Must be unique per episode. || custom || Optional || Not Required ||<br />
|-<br />
| crossId2 || Content originator (required only for distributors) || custom || Optional || Not Required ||<br />
|-<br />
| airdate || Original (local) air date and time (Eastern Time for US) || YYYYMMDD HH24:MI:SS || Mandatory || Not Required ||<br />
|-<br />
| pipmode ||<br />
Current state of picture-in-picture (PIP) mode on device.<br />
*"true" if audio / video measurement is displayed in PIP mode<br />
*"false" if audio / video measurement is displayed in regular mode<br />
|| "true", "false" || Optional || Not Required || Audio / Video Measurement<br />
|-<br />
| adloadtype ||<br />
Type of ad load:<br />
#Linear - matches TV ad load<br />
#Dynamic - Dynamic Ad Insertion (DAI)<br />
|| "1" - Linear<br />
"2" - Dynamic<br />
|| Mandatory || Not Required ||<br />
|-<br />
| progen || Genre (required only for non-TV originated content). See [[DCR OTT Genre List]] for acceptable values. || e.g. DD for Daytime Drama || Required for non-TV content || Not Required<br />
|}<br />
<br />
'''clientid & subbrand (vcid)'''<br />
By default, clientid and subbrand are setup in Nielsen backend configuration to capture brand and sub-brand information. The fields get populated from backend for a registered client appid. But if an app contains multiple brands and sub-brands and client is willing to give credit to another brand or sub-brand then :<br />
*Client app developer can override the clientid and subbrand (brand and sub-brand info.) in [[loadMetadata]] call to get a proper measurement for a desired brand and sub-brand.<br />
**If no clientid and subbrand are specified in CMS of a content and an ad, default values reported in the Configuration will be reported.<br />
**If clientid and subbrand are specified in CMS of the content or ad, the most recent clientid and subbrand will be reported in subsequent pings. To report with a different clientid and subbrand, include the new values for the keys in their subsequent [[loadMetadata]] call.<br />
'''Picture-in-picture (PIP) mode'''<br />
Once the app detects PIP mode, call [[loadMetadata]] with the same set of assetids, values and with one additional parameter "pipmode": "true". Once PIP mode is disabled in the device, call [[loadMetadata]] again with "pipmode":"false".<br />
<br />
==== Custom Variables Extension ====<br />
Contact Nielsen Technical Account Manager (TAM) to configure any custom variables, as needed for implementing the application.<br />
<br />
See [[Custom Variables Extension]] for more information.<br />
<br />
==== Advertisement Metadata ====<br />
{| class="wikitable"<br />
|-<br />
! Key !! Description !! Values !! Required for Audio / Video? !! Required for Static (page)<br />
|-<br />
| type || Type of ad || "preroll"<br />
"midroll"<br />
"postroll" <br />
|| Mandatory || Not Required<br />
|-<br />
| assetid || An ID assigned to the advertisement. Can be an internal ID, an ID provided by the ad server, or a randomly generated ID. Must be unique across all ads || custom || Mandatory || Not Required<br />
|-<br />
| title || Title of the advertisement || "MyAdName" || Mandatory || Not Required<br />
|}<br />
<br />
=== Passing Metadata ===<br />
The required metadata can be passed as key values through the loadMetadata method. The sample code below shows a metadata objects for various assets.<br />
'''Audio / Video Measurement Asset'''<br />
Type should be "content"<br />
<syntaxhighlight lang="json"> {<br />
"type": "content",<br />
"assetName": "myassetName",<br />
"length": "300",<br />
"title": "myTitle",<br />
"program": "myProgram",<br />
"censuscategory": "myCensusCategory",<br />
"assetid": "myAssetId",<br />
"channelName": "myChannel",<br />
"adloadtype": "2",<br />
"segB": "segmentB",<br />
"segC": "segmentC",<br />
"isfullepisode":"y",<br />
"crossId1": "Reference11",<br />
"crossId2": "Reference22",<br />
"airdate": "20161013 20:00:00"<br />
}</syntaxhighlight><br />
<br />
'''Static (Page) Measurement Asset'''<br />
Type should be "static"<br />
<syntaxhighlight lang="json"> {<br />
"type": "static",<br />
"assetid": "static123",<br />
"assetName": "Page-Asset",<br />
"section": "siteSection",<br />
"segA": "segmentA",<br />
"segB": "segmentB",<br />
"segC": "segmentC"<br />
}</syntaxhighlight><br />
<br />
'''For any of the ad types'''<br />
<syntaxhighlight lang="json"> {<br />
"type": "midroll",<br />
"length": "30",<br />
"assetid": "myMidrollAssetId",<br />
"adloadtype": "2",<br />
"tv": "true",<br />
"dataSrc": "cms"<br />
}</syntaxhighlight><br />
<blockquote>'''Note''': In case the individual ad details are not available, send ad pod (presence) details through the <code>loadMetadata</code> and playhead position through <code>setPlayheadPosition</code>.</blockquote><br />
<br />
'''loadMetadata'''<br />
The object can then be passed when calling [[loadMetadata]].<br />
<syntaxhighlight lang="java"> loadMetadata(jsonMetadataObject);</syntaxhighlight></div>AlexGutierrezhttps://engineeringportal.nielsen.com//w/index.php?title=DCR_Video_%26_Static_Cloud_API&diff=2261DCR Video & Static Cloud API2017-12-14T19:29:54Z<p>AlexGutierrez: </p>
<hr />
<div>{{Breadcrumb|}} {{Breadcrumb|Digital}} {{Breadcrumb|DCR & DTVR}} {{CurrentBreadcrumb}}<br />
[[Category:Digital]]<br />
<br />
This guide shows you how to integrate the Nielsen Cloud API to enable Digital Content Ratings (DCR) measurement on your over-the-top (OTT) Apps.<br />
*For Roku Apps, please see [[DCR Video & Static Roku Cloud API]]<br />
*For Mobile Apps, please see [[DCR Video & Static Mobile Cloud API]]<br />
<br />
==Prerequisites==<br />
To get started, you will need a Nielsen App ID. The App ID is a unique ID assigned to your app. This will be provided to you upon starting the integration.<br />
<br />
<syntaxhighlight lang="javascript">XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX</syntaxhighlight><br />
<br />
==Integration==<br />
We will cover the steps for constructing the Cloud API Calls.<br />
<br />
===Request Overview===<br />
<br />
====URL Structure====<br />
<br />
The Cloud API Calls are HTTP GET Requests with the URL structure:<br />
<br />
<syntaxhighlight lang="javascript">[endpoint]/[appid]/[sessionID]/a?b=[payload]</syntaxhighlight><br />
<br />
The URL includes the following components:<br />
<br />
*<code>[endpoint]</code>: location of data collection environment<br />
*<code>[appid]</code>: provided App ID<br />
*<code>[sessionID]</code>: unique value for each user session<br />
*<code>[payload]</code>: metadata and events<br />
<br />
====Endpoint====<br />
<br />
There are endpoints for testing and production:<br />
<br />
*Testing: <code>http://sandbox.cloudapi.imrworldwide.com/nmapi/v2/</code><br />
*Production: <code>https://cloudapi.imrworldwide.com/nmapi/v2/</code><br />
<br />
During testing, all calls should be pointed to the testing endpoint. We will review the update to the production endpoint during the Go Live section of this guide.<br />
<br />
====URL Example====<br />
As you move through the integration steps, you can reference the below URL structure with the expanded payload:<br />
<br />
<syntaxhighlight lang="javascript"><br />
http://sandbox.cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=<br />
{<br />
"devInfo": [deviceInfo],<br />
"metadata": {<br />
"static": [static_metadata],<br />
"content": [content_metadata],<br />
"ad": [ad metadata]<br />
},<br />
"event": [event],<br />
"position": [playhead_position],<br />
"type": [asset type],<br />
"utc": [UTC]<br />
}<br />
</syntaxhighlight><br />
<br />
===Create Session ID===<br />
A unique Session ID must be created upon app launch and provided in the URL. This will allow measurement to occur for the entire duration that a user is within the app.<br />
<br />
The session ID must be a GUID that is passed with every request and remain consistent throughout each individual session. You can use an existing ID, a random number, or date.now() as shown in the recommended sample code below:<br />
<br />
<syntaxhighlight lang="javascript">sessionID = Date.now()+String(Math.random()*1000000 >> 0);</syntaxhighlight><br />
<br />
Upon exiting the app, the session will need to be terminated using the delete event. Sessions will automatically expire after 30 minutes of cloud inactivity.<br />
<br />
===Define URL Structure===<br />
Define the URL structure using your provided <code>[appid]</code> and a unique <code>[sessionID]</code>.<br />
<br />
<syntaxhighlight lang="javascript">http://sandbox.cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=[payload]</syntaxhighlight><br />
<br />
===Configure Payload===<br />
<br />
All Cloud API requests must contain the following payload data:<br />
<br />
*''devInfo'': device and app info<br />
*''metadata'': asset metadata<br />
*''event metadata'': type of event<br />
<br />
The payload can be passed through key-values using the Nielsen reserved keys. The specific keys and descriptions are highlighted in the tables included in this section.<br />
<br />
'''Payload Example'''<br />
<br />
The example below should be referenced when following the steps for configuring the request payload.<br />
<br />
<syntaxhighlight lang="javascript"><br />
// 3.1 Configure Payload: devInfo <br />
payload = {<br />
"devInfo": {<br />
"devId": "AD-ID", <br />
"apn": "AppName",<br />
"apv": "1.0",<br />
"uoo": "false"<br />
},<br />
<br />
// 3.2 Configure Payload: metadata<br />
"metadata": {<br />
"static": {}, // object for measuring static content<br />
"content": { // object for measuring video content<br />
"type": "content", // "content" for video<br />
"assetid": "VIDEO-ID123", // unique ID for video<br />
"isfullepisode": "y", // full episode flag<br />
"program": "Program Name", // program name<br />
"title": "Episode Title S3 - EP1", // episode name<br />
"length": "1800", // content duration in seconds<br />
"segB": "Custom Segment B", // custom segment<br />
"segC": "Custom Segment C", // custom segment<br />
"crossId1": "Standard Episode ID", // episode ID<br />
"crossId2": "Content Originator ID", // content orginator (required for distributors)<br />
"airdate": "20161013 20:00:00", // airdate<br />
"adloadtype": "2" //ad load flag<br />
"hasAds": "1", // content contains ads = 1 / no ads = 0<br />
"progen": "CV" // program genre abbreviation<br />
},<br />
"ad": {<br />
"type": "preroll", // type of ad<br />
"assetid": "AD-ID123" // unique ID for ad<br />
}<br />
},<br />
<br />
// 3.3 Configure Payload: events<br />
"event": "playhead", //event name<br />
"position": "300", // position in seconds<br />
"type": "content", //"content" or "ad"<br />
"utc": "1456448742000" //unix timestamp in milliseconds <br />
}<br />
</syntaxhighlight><br />
<br />
=====Configure Payload: devInfo=====<br />
An object <code>"devInfo"</code> will need to be created to capture App and Device information.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| devId || unique ID to identify user (e.g. Advertising ID, Roku Device ID) || custom || Yes<br />
|-<br />
| apn || app name || custom || Yes<br />
|-<br />
| apv || app build version || custom || Yes<br />
|-<br />
| uoo || device opt-out status || <code>"true"</code> or <code>"false"</code> || Yes<br />
|-<br />
|}<br />
<br />
'''Example devInfo Object'''<br />
<syntaxhighlight lang="javascript"><br />
// create devInfo object<br />
"devInfo": {<br />
"devId": "AD-ID",<br />
"apn": "AppName",<br />
"apv": "1.0",<br />
"uoo": "false"<br />
},<br />
</syntaxhighlight><br />
<br />
==== 3.2 Configure Payload: metadata ====<br />
Asset metadata can be passed through <code>"metadata"</code>. There are two asset types: <code>"content"</code> for video and <code>"ad"</code> for ads. The metadata received for each asset is used for classification and reporting.<br />
<br />
You will need to set up <code>"metadata"</code> objects for <code>"content"</code> and <code>"ad"</code> with the required Nielsen keys as shown in the sample code below.<br />
<br />
===== Content Metadata =====<br />
Content metadata should remain constant throughout the entirety of an episode/clip including when ads play.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| type || type of asset || <code>"content"</code> || Yes<br />
|-<br />
| assetid || unique ID assigned to asset || custom || Yes<br />
|-<br />
| program ||name of program (25 character limit) || custom || Yes<br />
|-<br />
| title ||name of program (25 character limit) || custom || Yes<br />
|-<br />
| length || length of content in seconds || <code>seconds</code> (86400 for live stream) || Yes<br />
|-<br />
| segB || custom segment B || custom || <br />
|-<br />
| segC || custom segment C || custom || <br />
|-<br />
| airdate || the airdate in the linear TV || YYYYMMDD HH24:MI:SS || Yes<br />
|-<br />
| isfullepisode || full episode flag || <code>"y"</code>- full episode, <code>"n"</code>- non full episode || Yes<br />
|-<br />
| crossId1 || standard episode ID || custom || Yes<br />
|-<br />
| crossId2 || content originator (only required for distributors) || Nielsen ||<br />
|-<br />
| adloadtype || type of ad load:<br />
<code>"1"</code> Linear – matches TV ad load<br />
<br />
<code>"2"</code> Dynamic – Dynamic Ad Insertion (DAI)<br />
|| <code>"2"</code> - DCR measures content with dynamic ads || Yes<br />
|-<br />
| hasAds || ads indicator<br />
<code>"1"</code>: ads included<br />
<br />
<code>"0"</code>: ads not included<br />
|| <code>"1"</code> or <code>"0"</code> || Yes<br />
|-<br />
| progen || program genre abbreviation - see [[DCR OTT Genre List]] for accepted values || <code>"CV"</code> for Comedy Variety || Yes<br />
|}<br />
<br />
<br />
'''Example Content Object'''<br />
<syntaxhighlight lang='json'>// create content object<br />
"content": {<br />
"type": "content",<br />
"assetid": "VIDEO-ID123",<br />
"isfullepisode": "y",<br />
"program": "Program Name",<br />
"title": "Episode Title S3 - EP1",<br />
"length": "1800",<br />
"segB": "Custom Segment B",<br />
"segC": "Custom Segment C",<br />
"crossId1": "Standard Episode ID",<br />
"crossId2": "Content Originator ID",<br />
"airdate": "20161013 20:00:00",<br />
"adloadtype": "2",<br />
"hasAds": "1", <br />
"progen": "CV"<br />
}</syntaxhighlight><br />
<br />
===== Ad Metadata =====<br />
The ad metadata should be passed for each individual ad.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| type || type of ad || <code>"preroll"</code>, <code>"midroll"</code>, or <code>"postroll"</code> || Yes<br />
|-<br />
| assetid || unique ID assigned to ad || custom || Yes<br />
|}<br />
<br />
===== Example Ad Object =====<br />
<syntaxhighlight lang="javascript"><br />
// create ad object<br />
"ad": {<br />
"type": "preroll",<br />
"assetid": "AD-ID123"<br />
}<br />
</syntaxhighlight><br />
<br />
=== Configure Payload: Events ===<br />
<br />
The last part of the payload is for enabling events so content is measured correctly when viewed. The events and required parameters are included below.<br />
<br />
==== Event Types ====<br />
<br />
The available events are:<br />
{| class="wikitable"<br />
|-<br />
! Event !! Description<br />
|-<br />
| <code>"playhead"</code> || Playhead position in seconds. Must be passed as a whole number every 10 seconds. The final playhead position should also be sent before an asset has changed to properly capture full duration. Playhead is used to handle pause and scrubbing. When content is paused, stop passing playhead position.<br />
|-<br />
| <code>"complete"</code> || Complete event must be sent when the content has completed full playback or if a user has initiated a Stop. Before calling the complete event, a final playhead update with the final position is required to be sent to receive full duration credit. Complete does not need to be called if a user transitions videos (e.g. channel change or selecting a new VOD asset) before the video completes.<br />
|-<br />
| <code>"delete"</code> || The delete event should be sent when the application is exited and the viewing session is completed. After 30 minutes of inactivity, the session will expire automatically. All creditable duration will be summarized for all asset types when delete occurs (content and ads). Note that the delete event may not be feasible on certain platforms that do not offer the necessary callbacks to trigger this event.<br />
|}<br />
<br />
===== Event Parameters =====<br />
<br />
The following parameters need to be passed when calling events:<br />
<br />
{| class="wikitable"<br />
|-<br />
! Parameter !! Description !! Value !! Required<br />
|-<br />
| <code>"event"</code> || event type || <code>"playhead"</code>, <code>"complete"</code>, or <code>"delete"</code> || Yes<br />
|-<br />
| <code>"position"</code> || creditable position || playhead position in seconds or UTC timestamp in seconds for livestream || Yes<br />
|-<br />
| <code>"type"</code> || asset type || <code>"content"</code>, <code>"ad"</code> || Yes<br />
|-<br />
| <code>"utc"</code> || Unix timestamp in milliseconds. Must be passed every 10 seconds. || <code>"1472760000000"</code> || Yes<br />
|}<br />
<br />
===== Example Event =====<br />
You can call events by passing values in the required parameters:<br />
<br />
<syntaxhighlight lang="javascript"><br />
"devInfo": [deviceInfo],<br />
"metadata": {<br />
"static": [static metadata],<br />
"content": [content metadata],<br />
"ad": [ad metadata]<br />
},<br />
// Event Parameters<br />
"event": [event], // event name<br />
"position": [playheadPosition], //position in seconds<br />
"type": [asset type], // values are "content" or "ad"<br />
"utc": "1472760000000" //unix timestamp in milliseconds<br />
}<br />
</syntaxhighlight><br />
<br />
'''Note:''' The full payload including "devInfo" and "metadata" must be populated in each event request.<br />
<br />
===== Sample Event Lifecycle =====<br />
The sample event lifecycle can be used as a reference for identifying the order for calling events and values to pass.<br />
<br />
<syntaxhighlight lang="javascript"><br />
// Start of Session: session ID created when App is opened<br />
<br />
// Preroll<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
// Content<br />
Content Playhead {"event": "playhead", "position": "0", "type": "content", "utc": "1472760000000"} <br />
<br />
// Midroll<br />
Midroll Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
// Content resumes at 15 minutes<br />
Content Playhead {"event": "playhead", "position": "900", "type": "content", "utc": "1472760000000"} <br />
<br />
// Content completes at 30 minutes<br />
Complete {"event": "complete", "position": "1800", "type": "content", "utc": "1472760000000"} <br />
<br />
// Postroll<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
//End of Session: The delete event should be called when the App is exited. The values for position and type not required to be passed.<br />
Delete { "event": "delete", "position": "", "type": "", "utc": "1472760000000"} <br />
</syntaxhighlight><br />
<br />
<br />
'''Sample Event Lifecycle - Detailed Storyline'''<br />
This detailed event sequence provides additional insight for the correct events to call when handling certain playback scenarios.<br />
<syntaxhighlight lang='javascript'>// SESSION STARTS<br />
// Start of Session: session ID created when App is opened<br />
<br />
// PREROLL<br />
// Preroll Start - Start each Ad with a position of "0", resetting to '0' for each Ad, and Ad break.<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
// Preroll Stop - End each Ad with the final position of the Ad.<br />
Ad Playhead {"event": "playhead", "position": "15", "type": "ad", "utc": "1472761500000"}<br />
<br />
// CONTENT <br />
// Content Start - Start new content streams with a position of "0" incrementing the position every 10 seconds.<br />
Content Playhead {"event": "playhead", "position": "0", "type": "content", "utc": "1472761500000"} <br />
<br />
// Content Stop Before Ad Break - Send a playhead update including the current content positon before an Ad break.<br />
Content Playhead {"event": "playhead", "position": "299", "type": "content", "utc": "1472787400000"}<br />
<br />
// MIDROLL<br />
// Midroll Start - Start each Ad with a position of "0", resetting to '0' for each Ad, and Ad break.<br />
Midroll Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472787500000"} <br />
<br />
// Midroll Stop - End each Ad with the final position of the Ad.<br />
Ad Playhead {"event": "playhead", "position": "60", "type": "ad", "utc": "1472793500000"}<br />
<br />
// CONTENT<br />
// Content resumes at 5 minutes - Send playhead update with the current resumed position, and begin incrimenting the positon every 10 seconds.<br />
Content Playhead {"event": "playhead", "position": "300", "type": "content", "utc": "1472799500000"} <br />
<br />
// Content completes at 10:12 - Make sure to send in the playhead event with the final content position before sending the complete event.<br />
Final Content Playhead {"event": "playhead", "position": "612", "type": "content", "utc": "1472830700000"} <br />
<br />
Complete {"event": "complete", "position": "612", "type": "content", "utc": "1472830800000"} <br />
<br />
// POSTROLL<br />
// Postroll Start - Start each Ad with a position of "0", resetting to '0' for each Ad, and Ad break.<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472830900000"} <br />
<br />
// Postroll Stop - End each Ad with the final position of the Ad.<br />
Ad Playhead {"event": "playhead", "position": "45", "type": "ad", "utc": "1472835300000"}<br />
<br />
// SESSION ENDS<br />
<br />
//End of Session: The delete event should be called when the App is exited. The values for position and type not required to be passed.<br />
Delete { "event": "delete", "position": "", "type": "", "utc": "1472835400000"} </syntaxhighlight><br />
<br />
<br />
=====Handling Playhead=====<br />
Calling <code>"playhead"</code> is critical for accurate duration crediting. You can reference the below guidance to determine the correct playhead position to pass depending on the playback scenario.<br />
<br />
'''Playhead: General'''<br />
* Playhead position must start at 0 for each new asset, and be passed at least every 10 seconds.<br />
* Final postion must be sent at the end of content or an ad<br />
<br />
'''Playhead: Ads'''<br />
* The final position must be sent when switching from content to ad, or ad to content.<br />
* Each ad playhead position should be 0 at ad start.<br />
* For Ad Pods, playhead must be called, and reset to 0 for each individual ad. <br />
* The last content position before an Ad should be sent before switching to Ads.<br />
* When content has resumed following an ad break, the playhead position update must continue where the previous content segment left off.<br />
<br />
'''Playhead: User Actions'''<br />
* Upon user scrubbing, the current position must be sent before a user scrubs, and the new position should be sent where the user lands, and begin sending in the 10 second updates thereafter.<br />
* On pause, send the current position and then discontinue sending playhead event updates.<br />
* If a user exits a stream early, the last current position must be sent in a playhead update to receive accurate duration.<br />
<br />
===== Interruption Scenarios =====<br />
<br />
As part of configuring events, you will need to handle all possible interruption scenarios such as:<br />
<br />
*Wi-Fi OFF / ON<br />
*App going Background / Foreground (Video players only, not for Audio players)<br />
*App Crash or Exit<br />
<br />
When playback is interrupted, the app needs to send delete immediately.<br />
<br />
Once playback resumes, a new session will need to be created with a unique session ID. All of the required metadata and events will need to be sent.<br />
<br />
'''Note:''' The session will automatically timeout after 30 minutes of inactivity.<br />
<br />
=== Example Request ===<br />
<br />
Now that we walked through the Cloud API integration steps, your requests should have the following components: Session ID, App ID, and Payload. You can reference the example below when your reviewing your integration.<br />
<br />
<syntaxhighlight lang="javascript"><br />
// 1. Create Session ID<br />
sessionID = Date.now()+String(Math.random()*1000000 >> 0); // Must be GUID for each viewing session<br />
<br />
// 2. Define URL Structure with App ID and Session ID<br />
sessionURL = http://sandbox.cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=<br />
<br />
// 3. Configure Payload<br />
// 3.1 Configure Payload: devInfo <br />
payload = {<br />
"devInfo": {<br />
"devId": "AD-ID", <br />
"apn": "AppName",<br />
"apv": "1.0",<br />
"uoo": "false"<br />
},<br />
<br />
// 3.2 Configure Payload: metadata<br />
"metadata": {<br />
"static": {}, // object for measuring static content<br />
"content": { // object for measuring video content<br />
"type": "content", // "content" for video<br />
"assetid": "VIDEO-ID123", // unique ID for video<br />
"isfullepisode": "y", // full episode flag<br />
"program": "Program Name", // program name<br />
"title": "Episode Title S3 - EP1", // episode name<br />
"length": "1800", // content duration in seconds<br />
"segB": "Custom Segment B", // custom segment<br />
"segC": "Custom Segment C", // custom segment<br />
"crossId1": "Standard Episode ID", // episode ID<br />
"crossId2": "Content Originator ID", // content orginator (required for distributors)<br />
"airdate": "20161013 20:00:00", // airdate<br />
"adloadtype": "2", // ad load flag<br />
"hasAds": "1", // content contains ads = 1 / no ads = 0<br />
"progen": "CV" // program genre abbreviation<br />
},<br />
"ad": {<br />
"type": "preroll", // type of ad<br />
"assetid": "AD-ID123" // unique ID for ad<br />
}<br />
},<br />
<br />
// 3.3 Configure Payload: events<br />
"event": "playhead", //event name<br />
"position": "300", // position in seconds<br />
"type": "content", //"content" or "ad"<br />
"utc": "1456448742000" //unix timestamp in milliseconds <br />
}<br />
<br />
// Append payload to URL<br />
var image = new Image()<br />
image.onerror = function() {<br />
// wait and send again<br />
}<br />
(new Image).src = sessionURL+encodeURI(JSON.stringify(payload));<br />
</syntaxhighlight><br />
<br />
==== Enable Debug Logging ====<br />
<br />
Now that you have set up the Cloud API requests, you can enable debug logging to validate your integration. Enabling debug logging is required for Nielsen certification.<br />
<br />
==== GET Request ====<br />
<br />
Display GET Request to console using a name to identify each event (e.g. playhead).<br />
<br />
<syntaxhighlight lang="javascript"><br />
console.log("Event", image); <br />
</syntaxhighlight><br />
<br />
==== Payload ====<br />
<br />
Output payload to identify required metadata and events.<br />
<br />
<syntaxhighlight lang="javascript"><br />
console.log("Event Payload", payload); <br />
</syntaxhighlight><br />
<br />
==== HTTP Response Code ====<br />
<br />
Confirm request was completed by viewing HTTP response code.<br />
<br />
<syntaxhighlight lang="javascript"><br />
code = msg.GetResponseCode();<br />
console.log("Response Code", code); <br />
</syntaxhighlight><br />
<br />
You can reference the HTTP Response Code table when reviewing your requests:<br />
<br />
{| class="wikitable"<br />
|-<br />
! Status Code !! Status Text !! Description<br />
|-<br />
| <code>200</code> || OK || request received<br />
|-<br />
| <code>403</code> || Forbidden || invalid App ID<br />
|-<br />
| <code>404</code> || Not Found || JSON issue<br />
|}<br />
<br />
== Opt-Out ==<br />
Your app must provide a means for the user to Opt-Out, or Opt-In to Nielsen Measurement. The Opt-Out requirement can be fulfilled by creating an Opt-Out/Opt-In button, toggle switch, or slider within the app "Settings", or "About" section to allow the user selection. If you have the ability to render text within the Opt-Out screen, it is recommended to add the Privacy Information included below.<br />
<br />
[[File:Nielsen Opt-Out.png|link=]]<br />
<br />
You will need to store the User Opt-Out (uoo) status, so that it can be retrieved and populated in the <code>"devInfo"</code> metadata which will be sent in every Cloud API event (playhead, complete, & delete). <br />
<br />
*In addition, you will need to ensure that the devId field is a blank value if a user has elected to Opt-Out.<br />
<br />
*If the device offers "Limit Ad Tracking" or "Opt-Out" device settings, you must set uoo=true, and also ensure that the devId is set to a blank value if the user elects to enable this setting.<br />
<br />
{| class="wikitable"<br />
|-<br />
! uoo Key !! Description !! Values<br />
|-<br />
| uoo || Device is Opted-In to Nielsen Measurement (Recommended Default Setting) || <code>"false"</code><br />
|-<br />
| uoo || Device is Opted-Out of Nielsen Measurement || <code>"true"</code><br />
|}<br />
<br />
===== devInfo Opt-In JSON Payload Example =====<br />
<br />
<syntaxhighlight lang="javascript"><br />
"devInfo": {<br />
"apn": "Roku Sample App",<br />
"apv": "1",<br />
"devId": "7be25cf9-8c40-5cc2-871e-19bf41940288",<br />
"uoo": "false"<br />
},<br />
</syntaxhighlight><br />
<br />
===== devInfo Opt-Out JSON Payload Example =====<br />
<br />
<syntaxhighlight lang="javascript"><br />
"devInfo": {<br />
"apn": "Roku Sample App",<br />
"apv": "1",<br />
"devId": "", //devId must be blank when a user elects to Opt-Out.<br />
"uoo": "true"<br />
},<br />
</syntaxhighlight><br />
<br />
===== Privacy Information Template To Include In Opt-Out Screen =====<br />
<br />
<blockquote><br />
'''''ABOUT NIELSEN MEASUREMENT'''''<br />
<br />
Television and the way we watch it have come a long way since Nielsen began measuring TV audiences in 1950. Today, the ability to watch our favorite shows at any time and on multiple devices amplifies the need for exceptionally adept and flexible audience measurement capabilities.<br />
<br />
Consumers are changing with the times, and the same goes for us. As technology continues to evolve and media companies try new ways to attract viewers, understanding what consumers are watching — and what they're watching on — is more important than ever. Today, viewing video is a personal and mobile experience — anytime and anywhere. Our capabilities provide relevant metrics that are necessary to inform successful marketing and programming and drive continued growth. As a global information and measurement leader, we are committed to protecting the privacy and security of the data we collect, process and use. While our digital measurement products are not used to identify you in any way, they help us and our clients measure and analyze how consumers engage with media across online, mobile and emerging technologies, and offer insights into consumer behavior.<br />
<br />
'''''YOUR CHOICES'''''<br />
<br />
Nielsen believes that you should have a choice about whether to contribute to our research and insights. To opt out, or opt into Nielsen measurement please toggle your "Limit Ad Tracking" (or similar setting) on your device. If you have this app on more than one device, you will need to opt out of this app on each device. To learn more about our digital measurement products and your choices in regard to them, please visit http://www.nielsen.com/digitalprivacy.<br />
<br />
'''''Disclosure Template'''''<br />
<br />
Additionally, you must update the App description information from the App Distribution Store with the Nielsen Measurement disclosure below:<br />
<br />
This app features Nielsen's proprietary measurement software which will allow you to contribute to market research, like Nielsen's TV Ratings. Please see http://www.nielsen.com/digitalprivacy for more information.<br />
</blockquote><br />
<br />
== Testing ==<br />
Before providing an app build to Nielsen for testing, it is important to run validation checks once you have enabled debug logging.<br />
<br />
=== Payload Validation ===<br />
<br />
Ensure that all of the required payload data is populating while testing several videos. The following areas are critical to measurement:<br />
*devInfo<br />
*Asset metadata for both content, and ads<br />
*Events<br />
*Opt-Out status<br />
<br />
=== Player Events ===<br />
Review event calls:<br />
<br />
==== playhead ====<br />
*Playhead position updates every 10 seconds starting at position '0' for each new asset.<br />
*Final playhead position is sent on content, or ad before switching between assets.<br />
*Content metadata remains constant throughout an episode, or clip play.<br />
*Ad metadata is populated appropriately for each individual ad.<br />
*Playhead position update resumes for content after an ad break, and resets to 0 for each individual ad.<br />
*For scrubbing, last current position should be sent while scrubbing occurs, and the new position should also be sent where the user scrubs to.<br />
*Exiting a stream early should execute the last current position in a playhead update to receive accurate duration.<br />
*Upon pause, the current position should be sent, and playhead updates should stop incrementing until resume play occurs.<br />
<br />
==== complete ====<br />
*Check that the complete event executes upon content complete after the final playhead update is sent<br />
*Do not execute the complete event for ads<br />
<br />
==== delete ====<br />
*Check to see that the delete event occurs upon app exit, if the platform has the necessary exit callback events.<br />
<br />
==== GET Request Format ====<br />
*Ensure that the event payloads are formatted in JSON<br />
*Check to see that each of the Cloud API GET requests are properly encoded<br />
<br />
==== HTTP Response ====<br />
*Make sure that each of the Cloud API Get requests are received by the Nielsen Cloud API properly through use of the HTTP Response Code outputs enabled in console.<br />
<br />
==== Opt-Out ====<br />
*Test the "uoo" key gets populated accurately for both Opt-In and Opt-Out selections by validating the Cloud API events called after the user Opt-Out/Opt-In selection.<br />
*Test that the devId field is populated with a blank value if a user has elected to Opt-Out. For example: "devId": "",<br />
*If the device supports "Limit Ad Tracking" or has device "Opt-Out" settings, test that uoo=true, and that devId is set to a blank value if enabled in the device settings.<br />
<br />
== Go Live ==<br />
After your integration has been certified, you will need to: Change Endpoint and Disable Logging.<br />
<br />
'''Change Endpoint:''' You will need to update to the production endpoint:<br />
<br />
*Testing: <code>http://sandbox.cloudapi.imrworldwide.com/nmapi/v2/</code><br />
*Production: <code>https://cloudapi.imrworldwide.com/nmapi/v2/</code><br />
<br />
Your production URL structure should now be:<br />
<code>https://cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=[payload]</code><br />
<br />
'''Disable Logging:''' You can now disable debug logging</div>AlexGutierrezhttps://engineeringportal.nielsen.com//w/index.php?title=DCR_Video_%26_Static_Cloud_API&diff=2260DCR Video & Static Cloud API2017-12-13T21:45:41Z<p>AlexGutierrez: </p>
<hr />
<div>{{Breadcrumb|}} {{Breadcrumb|Digital}} {{Breadcrumb|DCR & DTVR}} {{CurrentBreadcrumb}}<br />
[[Category:Digital]]<br />
<br />
This guide shows you how to integrate the Nielsen Cloud API to enable Digital Content Ratings (DCR) measurement on your over-the-top (OTT) Apps.<br />
*For Roku Apps, please see [[DCR Video & Static Roku Cloud API]]<br />
*For Mobile Apps, please see [[DCR Video & Static Mobile Cloud API]]<br />
<br />
==Prerequisites==<br />
To get started, you will need a Nielsen App ID. The App ID is a unique ID assigned to your app. This will be provided to you upon starting the integration.<br />
<br />
<syntaxhighlight lang="javascript">XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX</syntaxhighlight><br />
<br />
==Integration==<br />
We will cover the steps for constructing the Cloud API Calls.<br />
<br />
===Request Overview===<br />
<br />
====URL Structure====<br />
<br />
The Cloud API Calls are HTTP GET Requests with the URL structure:<br />
<br />
<syntaxhighlight lang="javascript">[endpoint]/[appid]/[sessionID]/a?b=[payload]</syntaxhighlight><br />
<br />
The URL includes the following components:<br />
<br />
*<code>[endpoint]</code>: location of data collection environment<br />
*<code>[appid]</code>: provided App ID<br />
*<code>[sessionID]</code>: unique value for each user session<br />
*<code>[payload]</code>: metadata and events<br />
<br />
====Endpoint====<br />
<br />
There are endpoints for testing and production:<br />
<br />
*Testing: <code>http://sandbox.cloudapi.imrworldwide.com/nmapi/v2/</code><br />
*Production: <code>https://cloudapi.imrworldwide.com/nmapi/v2/</code><br />
<br />
During testing, all calls should be pointed to the testing endpoint. We will review the update to the production endpoint during the Go Live section of this guide.<br />
<br />
====URL Example====<br />
As you move through the integration steps, you can reference the below URL structure with the expanded payload:<br />
<br />
<syntaxhighlight lang="javascript"><br />
http://sandbox.cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=<br />
{<br />
"devInfo": [deviceInfo],<br />
"metadata": {<br />
"static": [static_metadata],<br />
"content": [content_metadata],<br />
"ad": [ad metadata]<br />
},<br />
"event": [event],<br />
"position": [playhead_position],<br />
"type": [asset type],<br />
"utc": [UTC]<br />
}<br />
</syntaxhighlight><br />
<br />
===Create Session ID===<br />
A unique Session ID must be created upon app launch and provided in the URL. This will allow measurement to occur for the entire duration that a user is within the app.<br />
<br />
The session ID must be a GUID that is passed with every request and remain consistent throughout each individual session. You can use an existing ID, a random number, or date.now() as shown in the recommended sample code below:<br />
<br />
<syntaxhighlight lang="javascript">sessionID = Date.now()+String(Math.random()*1000000 >> 0);</syntaxhighlight><br />
<br />
Upon exiting the app, the session will need to be terminated using the delete event. Sessions will automatically expire after 30 minutes of cloud inactivity.<br />
<br />
===Define URL Structure===<br />
Define the URL structure using your provided <code>[appid]</code> and a unique <code>[sessionID]</code>.<br />
<br />
<syntaxhighlight lang="javascript">http://sandbox.cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=[payload]</syntaxhighlight><br />
<br />
===Configure Payload===<br />
<br />
All Cloud API requests must contain the following payload data:<br />
<br />
*''devInfo'': device and app info<br />
*''metadata'': asset metadata<br />
*''event metadata'': type of event<br />
<br />
The payload can be passed through key-values using the Nielsen reserved keys. The specific keys and descriptions are highlighted in the tables included in this section.<br />
<br />
'''Payload Example'''<br />
<br />
The example below should be referenced when following the steps for configuring the request payload.<br />
<br />
<syntaxhighlight lang="javascript"><br />
// 3.1 Configure Payload: devInfo <br />
payload = {<br />
"devInfo": {<br />
"devId": "AD-ID", <br />
"apn": "AppName",<br />
"apv": "1.0",<br />
"uoo": "false"<br />
},<br />
<br />
// 3.2 Configure Payload: metadata<br />
"metadata": {<br />
"static": {}, // object for measuring static content<br />
"content": { // object for measuring video content<br />
"type": "content", // "content" for video<br />
"assetid": "VIDEO-ID123", // unique ID for video<br />
"isfullepisode": "y", // full episode flag<br />
"program": "Program Name", // program name<br />
"title": "Episode Title S3 - EP1", // episode name<br />
"length": "1800", // content duration in seconds<br />
"segB": "Custom Segment B", // custom segment<br />
"segC": "Custom Segment C", // custom segment<br />
"crossId1": "Standard Episode ID", // episode ID<br />
"crossId2": "Content Originator ID", // content orginator (required for distributors)<br />
"airdate": "20161013 20:00:00", // airdate<br />
"adloadtype": "2" //ad load flag<br />
"hasAds": "1", // content contains ads = 1 / no ads = 0<br />
"progen": "CV" // program genre abbreviation<br />
},<br />
"ad": {<br />
"type": "preroll", // type of ad<br />
"assetid": "AD-ID123" // unique ID for ad<br />
}<br />
},<br />
<br />
// 3.3 Configure Payload: events<br />
"event": "playhead", //event name<br />
"position": "300", // position in seconds<br />
"type": "content", //"content" or "ad"<br />
"utc": "1456448742000" //unix timestamp in milliseconds <br />
}<br />
</syntaxhighlight><br />
<br />
=====Configure Payload: devInfo=====<br />
An object <code>"devInfo"</code> will need to be created to capture App and Device information.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| devId || unique ID to identify user (e.g. Advertising ID, Roku Device ID) || custom || Yes<br />
|-<br />
| apn || app name || custom || Yes<br />
|-<br />
| apv || app build version || custom || Yes<br />
|-<br />
| uoo || device opt-out status || <code>"true"</code> or <code>"false"</code> || Yes<br />
|-<br />
|}<br />
<br />
'''Example devInfo Object'''<br />
<syntaxhighlight lang="javascript"><br />
// create devInfo object<br />
"devInfo": {<br />
"devId": "AD-ID",<br />
"apn": "AppName",<br />
"apv": "1.0",<br />
"uoo": "false"<br />
},<br />
</syntaxhighlight><br />
<br />
==== 3.2 Configure Payload: metadata ====<br />
Asset metadata can be passed through <code>"metadata"</code>. There are two asset types: <code>"content"</code> for video and <code>"ad"</code> for ads. The metadata received for each asset is used for classification and reporting.<br />
<br />
You will need to set up <code>"metadata"</code> objects for <code>"content"</code> and <code>"ad"</code> with the required Nielsen keys as shown in the sample code below.<br />
<br />
===== Content Metadata =====<br />
Content metadata should remain constant throughout the entirety of an episode/clip including when ads play.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| type || type of asset || <code>"content"</code> || Yes<br />
|-<br />
| assetid || unique ID assigned to asset || custom || Yes<br />
|-<br />
| program ||name of program (25 character limit) || custom || Yes<br />
|-<br />
| title ||name of program (25 character limit) || custom || Yes<br />
|-<br />
| length || length of content in seconds || <code>seconds</code> (86400 for live stream) || Yes<br />
|-<br />
| segB || custom segment B || custom || <br />
|-<br />
| segC || custom segment C || custom || <br />
|-<br />
| airdate || the airdate in the linear TV || YYYYMMDD HH24:MI:SS || Yes<br />
|-<br />
| isfullepisode || full episode flag || <code>"y"</code>- full episode, <code>"n"</code>- non full episode || Yes<br />
|-<br />
| crossId1 || standard episode ID || custom || Yes<br />
|-<br />
| crossId2 || content originator (only required for distributors) || Nielsen ||<br />
|-<br />
| adloadtype || type of ad load:<br />
<code>"1"</code> Linear – matches TV ad load<br />
<br />
<code>"2"</code> Dynamic – Dynamic Ad Insertion (DAI)<br />
|| <code>"2"</code> - DCR measures content with dynamic ads || Yes<br />
|-<br />
| hasAds || ads indicator<br />
<code>"1"</code>: ads included<br />
<br />
<code>"0"</code>: ads not included<br />
|| <code>"1"</code> or <code>"0"</code> || Yes<br />
|-<br />
| progen || program genre abbreviation - see [[DCR OTT Genre List]] for accepted values || <code>"CV"</code> for Comedy Variety || Yes<br />
|}<br />
<br />
<br />
'''Example Content Object'''<br />
<syntaxhighlight lang='json'>// create content object<br />
"content": {<br />
"type": "content",<br />
"assetid": "VIDEO-ID123",<br />
"isfullepisode": "y",<br />
"program": "Program Name",<br />
"title": "Episode Title S3 - EP1",<br />
"length": "1800",<br />
"segB": "Custom Segment B",<br />
"segC": "Custom Segment C",<br />
"crossId1": "Standard Episode ID",<br />
"crossId2": "Content Originator ID",<br />
"airdate": "20161013 20:00:00",<br />
"adloadtype": "2",<br />
"hasAds": "1", <br />
"progen": "CV"<br />
}</syntaxhighlight><br />
<br />
===== Ad Metadata =====<br />
The ad metadata should be passed for each individual ad.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| type || type of ad || <code>"preroll"</code>, <code>"midroll"</code>, or <code>"postroll"</code> || Yes<br />
|-<br />
| assetid || unique ID assigned to ad || custom || Yes<br />
|}<br />
<br />
===== Example Ad Object =====<br />
<syntaxhighlight lang="javascript"><br />
// create ad object<br />
"ad": {<br />
"type": "preroll",<br />
"assetid": "AD-ID123"<br />
}<br />
</syntaxhighlight><br />
<br />
=== Configure Payload: Events ===<br />
<br />
The last part of the payload is for enabling events so content is measured correctly when viewed. The events and required parameters are included below.<br />
<br />
==== Event Types ====<br />
<br />
The available events are:<br />
{| class="wikitable"<br />
|-<br />
! Event !! Description<br />
|-<br />
| <code>"playhead"</code> || Playhead position in seconds. Must be passed as a whole number every 10 seconds. The final playhead position should also be sent before an asset has changed to properly capture full duration. Playhead is used to handle pause and scrubbing. When content is paused, stop passing playhead position.<br />
|-<br />
| <code>"complete"</code> || Complete event must be sent when the content has completed full playback.Before calling the complete event, a final playhead update with the final position is required to be sent to receive full duration credit.<br />
|-<br />
| <code>"delete"</code> || The delete event must be sent when the session is completed, or terminated. After 30 minutes of inactivity, the session will expire. All creditable duration will be summarized for all asset types when delete occurs (content and ads).<br />
|}<br />
<br />
===== Event Parameters =====<br />
<br />
The following parameters need to be passed when calling events:<br />
<br />
{| class="wikitable"<br />
|-<br />
! Parameter !! Description !! Value !! Required<br />
|-<br />
| <code>"event"</code> || event type || <code>"playhead"</code>, <code>"complete"</code>, or <code>"delete"</code> || Yes<br />
|-<br />
| <code>"position"</code> || creditable position || playhead position in seconds or UTC timestamp in seconds for livestream || Yes<br />
|-<br />
| <code>"type"</code> || asset type || <code>"content"</code>, <code>"ad"</code> || Yes<br />
|-<br />
| <code>"utc"</code> || Unix timestamp in milliseconds. Must be passed every 10 seconds. || <code>"1472760000000"</code> || Yes<br />
|}<br />
<br />
===== Example Event =====<br />
You can call events by passing values in the required parameters:<br />
<br />
<syntaxhighlight lang="javascript"><br />
"devInfo": [deviceInfo],<br />
"metadata": {<br />
"static": [static metadata],<br />
"content": [content metadata],<br />
"ad": [ad metadata]<br />
},<br />
// Event Parameters<br />
"event": [event], // event name<br />
"position": [playheadPosition], //position in seconds<br />
"type": [asset type], // values are "content" or "ad"<br />
"utc": "1472760000000" //unix timestamp in milliseconds<br />
}<br />
</syntaxhighlight><br />
<br />
'''Note:''' The full payload including "devInfo" and "metadata" must be populated in each event request.<br />
<br />
===== Sample Event Lifecycle =====<br />
The sample event lifecycle can be used as a reference for identifying the order for calling events and values to pass.<br />
<br />
<syntaxhighlight lang="javascript"><br />
// Start of Session: session ID created when App is opened<br />
<br />
// Preroll<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
// Content<br />
Content Playhead {"event": "playhead", "position": "0", "type": "content", "utc": "1472760000000"} <br />
<br />
// Midroll<br />
Midroll Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
// Content resumes at 15 minutes<br />
Content Playhead {"event": "playhead", "position": "900", "type": "content", "utc": "1472760000000"} <br />
<br />
// Content completes at 30 minutes<br />
Complete {"event": "complete", "position": "1800", "type": "content", "utc": "1472760000000"} <br />
<br />
// Postroll<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
//End of Session: The delete event should be called when the App is exited. The values for position and type not required to be passed.<br />
Delete { "event": "delete", "position": "", "type": "", "utc": "1472760000000"} <br />
</syntaxhighlight><br />
<br />
<br />
'''Sample Event Lifecycle - Detailed Storyline'''<br />
This detailed event sequence provides additional insight for the correct events to call when handling certain playback scenarios.<br />
<syntaxhighlight lang='javascript'>// SESSION STARTS<br />
// Start of Session: session ID created when App is opened<br />
<br />
// PREROLL<br />
// Preroll Start - Start each Ad with a position of "0", resetting to '0' for each Ad, and Ad break.<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
// Preroll Stop - End each Ad with the final position of the Ad.<br />
Ad Playhead {"event": "playhead", "position": "15", "type": "ad", "utc": "1472761500000"}<br />
<br />
// CONTENT <br />
// Content Start - Start new content streams with a position of "0" incrementing the position every 10 seconds.<br />
Content Playhead {"event": "playhead", "position": "0", "type": "content", "utc": "1472761500000"} <br />
<br />
// Content Stop Before Ad Break - Send a playhead update including the current content positon before an Ad break.<br />
Content Playhead {"event": "playhead", "position": "299", "type": "content", "utc": "1472787400000"}<br />
<br />
// MIDROLL<br />
// Midroll Start - Start each Ad with a position of "0", resetting to '0' for each Ad, and Ad break.<br />
Midroll Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472787500000"} <br />
<br />
// Midroll Stop - End each Ad with the final position of the Ad.<br />
Ad Playhead {"event": "playhead", "position": "60", "type": "ad", "utc": "1472793500000"}<br />
<br />
// CONTENT<br />
// Content resumes at 5 minutes - Send playhead update with the current resumed position, and begin incrimenting the positon every 10 seconds.<br />
Content Playhead {"event": "playhead", "position": "300", "type": "content", "utc": "1472799500000"} <br />
<br />
// Content completes at 10:12 - Make sure to send in the playhead event with the final content position before sending the complete event.<br />
Final Content Playhead {"event": "playhead", "position": "612", "type": "content", "utc": "1472830700000"} <br />
<br />
Complete {"event": "complete", "position": "612", "type": "content", "utc": "1472830800000"} <br />
<br />
// POSTROLL<br />
// Postroll Start - Start each Ad with a position of "0", resetting to '0' for each Ad, and Ad break.<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472830900000"} <br />
<br />
// Postroll Stop - End each Ad with the final position of the Ad.<br />
Ad Playhead {"event": "playhead", "position": "45", "type": "ad", "utc": "1472835300000"}<br />
<br />
// SESSION ENDS<br />
<br />
//End of Session: The delete event should be called when the App is exited. The values for position and type not required to be passed.<br />
Delete { "event": "delete", "position": "", "type": "", "utc": "1472835400000"} </syntaxhighlight><br />
<br />
<br />
=====Handling Playhead=====<br />
Calling <code>"playhead"</code> is critical for accurate duration crediting. You can reference the below guidance to determine the correct playhead position to pass depending on the playback scenario.<br />
<br />
'''Playhead: General'''<br />
* Playhead position must start at 0 for each new asset, and be passed at least every 10 seconds.<br />
* Final postion must be sent at the end of content or an ad<br />
<br />
'''Playhead: Ads'''<br />
* The final position must be sent when switching from content to ad, or ad to content.<br />
* Each ad playhead position should be 0 at ad start.<br />
* For Ad Pods, playhead must be called, and reset to 0 for each individual ad. <br />
* The last content position before an Ad should be sent before switching to Ads.<br />
* When content has resumed following an ad break, the playhead position update must continue where the previous content segment left off.<br />
<br />
'''Playhead: User Actions'''<br />
* Upon user scrubbing, the current position must be sent before a user scrubs, and the new position should be sent where the user lands, and begin sending in the 10 second updates thereafter.<br />
* On pause, send the current position and then discontinue sending playhead event updates.<br />
* If a user exits a stream early, the last current position must be sent in a playhead update to receive accurate duration.<br />
<br />
===== Interruption Scenarios =====<br />
<br />
As part of configuring events, you will need to handle all possible interruption scenarios such as:<br />
<br />
*Wi-Fi OFF / ON<br />
*App going Background / Foreground (Video players only, not for Audio players)<br />
*App Crash or Exit<br />
<br />
When playback is interrupted, the app needs to send delete immediately.<br />
<br />
Once playback resumes, a new session will need to be created with a unique session ID. All of the required metadata and events will need to be sent.<br />
<br />
'''Note:''' The session will automatically timeout after 30 minutes of inactivity.<br />
<br />
=== Example Request ===<br />
<br />
Now that we walked through the Cloud API integration steps, your requests should have the following components: Session ID, App ID, and Payload. You can reference the example below when your reviewing your integration.<br />
<br />
<syntaxhighlight lang="javascript"><br />
// 1. Create Session ID<br />
sessionID = Date.now()+String(Math.random()*1000000 >> 0); // Must be GUID for each viewing session<br />
<br />
// 2. Define URL Structure with App ID and Session ID<br />
sessionURL = http://sandbox.cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=<br />
<br />
// 3. Configure Payload<br />
// 3.1 Configure Payload: devInfo <br />
payload = {<br />
"devInfo": {<br />
"devId": "AD-ID", <br />
"apn": "AppName",<br />
"apv": "1.0",<br />
"uoo": "false"<br />
},<br />
<br />
// 3.2 Configure Payload: metadata<br />
"metadata": {<br />
"static": {}, // object for measuring static content<br />
"content": { // object for measuring video content<br />
"type": "content", // "content" for video<br />
"assetid": "VIDEO-ID123", // unique ID for video<br />
"isfullepisode": "y", // full episode flag<br />
"program": "Program Name", // program name<br />
"title": "Episode Title S3 - EP1", // episode name<br />
"length": "1800", // content duration in seconds<br />
"segB": "Custom Segment B", // custom segment<br />
"segC": "Custom Segment C", // custom segment<br />
"crossId1": "Standard Episode ID", // episode ID<br />
"crossId2": "Content Originator ID", // content orginator (required for distributors)<br />
"airdate": "20161013 20:00:00", // airdate<br />
"adloadtype": "2", // ad load flag<br />
"hasAds": "1", // content contains ads = 1 / no ads = 0<br />
"progen": "CV" // program genre abbreviation<br />
},<br />
"ad": {<br />
"type": "preroll", // type of ad<br />
"assetid": "AD-ID123" // unique ID for ad<br />
}<br />
},<br />
<br />
// 3.3 Configure Payload: events<br />
"event": "playhead", //event name<br />
"position": "300", // position in seconds<br />
"type": "content", //"content" or "ad"<br />
"utc": "1456448742000" //unix timestamp in milliseconds <br />
}<br />
<br />
// Append payload to URL<br />
var image = new Image()<br />
image.onerror = function() {<br />
// wait and send again<br />
}<br />
(new Image).src = sessionURL+encodeURI(JSON.stringify(payload));<br />
</syntaxhighlight><br />
<br />
==== Enable Debug Logging ====<br />
<br />
Now that you have set up the Cloud API requests, you can enable debug logging to validate your integration. Enabling debug logging is required for Nielsen certification.<br />
<br />
==== GET Request ====<br />
<br />
Display GET Request to console using a name to identify each event (e.g. playhead).<br />
<br />
<syntaxhighlight lang="javascript"><br />
console.log("Event", image); <br />
</syntaxhighlight><br />
<br />
==== Payload ====<br />
<br />
Output payload to identify required metadata and events.<br />
<br />
<syntaxhighlight lang="javascript"><br />
console.log("Event Payload", payload); <br />
</syntaxhighlight><br />
<br />
==== HTTP Response Code ====<br />
<br />
Confirm request was completed by viewing HTTP response code.<br />
<br />
<syntaxhighlight lang="javascript"><br />
code = msg.GetResponseCode();<br />
console.log("Response Code", code); <br />
</syntaxhighlight><br />
<br />
You can reference the HTTP Response Code table when reviewing your requests:<br />
<br />
{| class="wikitable"<br />
|-<br />
! Status Code !! Status Text !! Description<br />
|-<br />
| <code>200</code> || OK || request received<br />
|-<br />
| <code>403</code> || Forbidden || invalid App ID<br />
|-<br />
| <code>404</code> || Not Found || JSON issue<br />
|}<br />
<br />
== Opt-Out ==<br />
Your app must provide a means for the user to Opt-Out, or Opt-In to Nielsen Measurement. The Opt-Out requirement can be fulfilled by creating an Opt-Out/Opt-In button, toggle switch, or slider within the app "Settings", or "About" section to allow the user selection. If you have the ability to render text within the Opt-Out screen, it is recommended to add the Privacy Information included below.<br />
<br />
[[File:Nielsen Opt-Out.png|link=]]<br />
<br />
You will need to store the User Opt-Out (uoo) status, so that it can be retrieved and populated in the <code>"devInfo"</code> metadata which will be sent in every Cloud API event (playhead, complete, & delete). <br />
<br />
*In addition, you will need to ensure that the devId field is a blank value if a user has elected to Opt-Out.<br />
<br />
*If the device offers "Limit Ad Tracking" or "Opt-Out" device settings, you must set uoo=true, and also ensure that the devId is set to a blank value if the user elects to enable this setting.<br />
<br />
{| class="wikitable"<br />
|-<br />
! uoo Key !! Description !! Values<br />
|-<br />
| uoo || Device is Opted-In to Nielsen Measurement (Recommended Default Setting) || <code>"false"</code><br />
|-<br />
| uoo || Device is Opted-Out of Nielsen Measurement || <code>"true"</code><br />
|}<br />
<br />
===== devInfo Opt-In JSON Payload Example =====<br />
<br />
<syntaxhighlight lang="javascript"><br />
"devInfo": {<br />
"apn": "Roku Sample App",<br />
"apv": "1",<br />
"devId": "7be25cf9-8c40-5cc2-871e-19bf41940288",<br />
"uoo": "false"<br />
},<br />
</syntaxhighlight><br />
<br />
===== devInfo Opt-Out JSON Payload Example =====<br />
<br />
<syntaxhighlight lang="javascript"><br />
"devInfo": {<br />
"apn": "Roku Sample App",<br />
"apv": "1",<br />
"devId": "", //devId must be blank when a user elects to Opt-Out.<br />
"uoo": "true"<br />
},<br />
</syntaxhighlight><br />
<br />
===== Privacy Information Template To Include In Opt-Out Screen =====<br />
<br />
<blockquote><br />
'''''ABOUT NIELSEN MEASUREMENT'''''<br />
<br />
Television and the way we watch it have come a long way since Nielsen began measuring TV audiences in 1950. Today, the ability to watch our favorite shows at any time and on multiple devices amplifies the need for exceptionally adept and flexible audience measurement capabilities.<br />
<br />
Consumers are changing with the times, and the same goes for us. As technology continues to evolve and media companies try new ways to attract viewers, understanding what consumers are watching — and what they're watching on — is more important than ever. Today, viewing video is a personal and mobile experience — anytime and anywhere. Our capabilities provide relevant metrics that are necessary to inform successful marketing and programming and drive continued growth. As a global information and measurement leader, we are committed to protecting the privacy and security of the data we collect, process and use. While our digital measurement products are not used to identify you in any way, they help us and our clients measure and analyze how consumers engage with media across online, mobile and emerging technologies, and offer insights into consumer behavior.<br />
<br />
'''''YOUR CHOICES'''''<br />
<br />
Nielsen believes that you should have a choice about whether to contribute to our research and insights. To opt out, or opt into Nielsen measurement please toggle your "Limit Ad Tracking" (or similar setting) on your device. If you have this app on more than one device, you will need to opt out of this app on each device. To learn more about our digital measurement products and your choices in regard to them, please visit http://www.nielsen.com/digitalprivacy.<br />
<br />
'''''Disclosure Template'''''<br />
<br />
Additionally, you must update the App description information from the App Distribution Store with the Nielsen Measurement disclosure below:<br />
<br />
This app features Nielsen's proprietary measurement software which will allow you to contribute to market research, like Nielsen's TV Ratings. Please see http://www.nielsen.com/digitalprivacy for more information.<br />
</blockquote><br />
<br />
== Testing ==<br />
Before providing an app build to Nielsen for testing, it is important to run validation checks once you have enabled debug logging.<br />
<br />
=== Payload Validation ===<br />
<br />
Ensure that all of the required payload data is populating while testing several videos. The following areas are critical to measurement:<br />
*devInfo<br />
*Asset metadata for both content, and ads<br />
*Events<br />
*Opt-Out status<br />
<br />
=== Player Events ===<br />
Review event calls:<br />
<br />
==== playhead ====<br />
*Playhead position updates every 10 seconds starting at position '0' for each new asset.<br />
*Final playhead position is sent on content, or ad before switching between assets.<br />
*Content metadata remains constant throughout an episode, or clip play.<br />
*Ad metadata is populated appropriately for each individual ad.<br />
*Playhead position update resumes for content after an ad break, and resets to 0 for each individual ad.<br />
*For scrubbing, last current position should be sent while scrubbing occurs, and the new position should also be sent where the user scrubs to.<br />
*Exiting a stream early should execute the last current position in a playhead update to receive accurate duration.<br />
*Upon pause, the current position should be sent, and playhead updates should stop incrementing until resume play occurs.<br />
<br />
==== complete ====<br />
*Check that the complete event executes upon content complete after the final playhead update is sent<br />
*Do not execute the complete event for ads<br />
<br />
==== delete ====<br />
*Check to see that the delete event occurs upon app exit, if the platform has the necessary exit callback events.<br />
<br />
==== GET Request Format ====<br />
*Ensure that the event payloads are formatted in JSON<br />
*Check to see that each of the Cloud API GET requests are properly encoded<br />
<br />
==== HTTP Response ====<br />
*Make sure that each of the Cloud API Get requests are received by the Nielsen Cloud API properly through use of the HTTP Response Code outputs enabled in console.<br />
<br />
==== Opt-Out ====<br />
*Test the "uoo" key gets populated accurately for both Opt-In and Opt-Out selections by validating the Cloud API events called after the user Opt-Out/Opt-In selection.<br />
*Test that the devId field is populated with a blank value if a user has elected to Opt-Out. For example: "devId": "",<br />
*If the device supports "Limit Ad Tracking" or has device "Opt-Out" settings, test that uoo=true, and that devId is set to a blank value if enabled in the device settings.<br />
<br />
== Go Live ==<br />
After your integration has been certified, you will need to: Change Endpoint and Disable Logging.<br />
<br />
'''Change Endpoint:''' You will need to update to the production endpoint:<br />
<br />
*Testing: <code>http://sandbox.cloudapi.imrworldwide.com/nmapi/v2/</code><br />
*Production: <code>https://cloudapi.imrworldwide.com/nmapi/v2/</code><br />
<br />
Your production URL structure should now be:<br />
<code>https://cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=[payload]</code><br />
<br />
'''Disable Logging:''' You can now disable debug logging</div>AlexGutierrezhttps://engineeringportal.nielsen.com//w/index.php?title=DCR_Video_%26_Static_Cloud_API&diff=2218DCR Video & Static Cloud API2017-12-12T21:32:42Z<p>AlexGutierrez: </p>
<hr />
<div>{{Breadcrumb|}} {{Breadcrumb|Digital}} {{Breadcrumb|DCR & DTVR}} {{CurrentBreadcrumb}}<br />
[[Category:Digital]]<br />
<br />
This guide shows you how to integrate the Nielsen Cloud API to enable Digital Content Ratings (DCR) measurement on your over-the-top (OTT) Apps.<br />
*For Roku Apps, please see [[DCR Video & Static Roku Cloud API]]<br />
*For Mobile Apps, please see [[DCR Video & Static Mobile Cloud API]]<br />
<br />
==Prerequisites==<br />
To get started, you will need a Nielsen App ID. The App ID is a unique ID assigned to your app. This will be provided to you upon starting the integration.<br />
<br />
<syntaxhighlight lang="javascript">XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX</syntaxhighlight><br />
<br />
==Integration==<br />
We will cover the steps for constructing the Cloud API Calls.<br />
<br />
===Request Overview===<br />
<br />
====URL Structure====<br />
<br />
The Cloud API Calls are HTTP GET Requests with the URL structure:<br />
<br />
<syntaxhighlight lang="javascript">[endpoint]/[appid]/[sessionID]/a?b=[payload]</syntaxhighlight><br />
<br />
The URL includes the following components:<br />
<br />
*<code>[endpoint]</code>: location of data collection environment<br />
*<code>[appid]</code>: provided App ID<br />
*<code>[sessionID]</code>: unique value for each user session<br />
*<code>[payload]</code>: metadata and events<br />
<br />
====Endpoint====<br />
<br />
There are endpoints for testing and production:<br />
<br />
*Testing: <code>http://sandbox.cloudapi.imrworldwide.com/nmapi/v2/</code><br />
*Production: <code>https://cloudapi.imrworldwide.com/nmapi/v2/</code><br />
<br />
During testing, all calls should be pointed to the testing endpoint. We will review the update to the production endpoint during the Go Live section of this guide.<br />
<br />
====URL Example====<br />
As you move through the integration steps, you can reference the below URL structure with the expanded payload:<br />
<br />
<syntaxhighlight lang="javascript"><br />
http://sandbox.cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=<br />
{<br />
"devInfo": [deviceInfo],<br />
"metadata": {<br />
"static": [static_metadata],<br />
"content": [content_metadata],<br />
"ad": [ad metadata]<br />
},<br />
"event": [event],<br />
"position": [playhead_position],<br />
"type": [asset type],<br />
"utc": [UTC]<br />
}<br />
</syntaxhighlight><br />
<br />
===Create Session ID===<br />
A unique Session ID must be created upon app launch and provided in the URL. This will allow measurement to occur for the entire duration that a user is within the app.<br />
<br />
The session ID must be a GUID that is passed with every request and remain consistent throughout each individual session. You can use an existing ID, a random number, or date.now() as shown in the recommended sample code below:<br />
<br />
<syntaxhighlight lang="javascript">sessionID = Date.now()+String(Math.random()*1000000 >> 0);</syntaxhighlight><br />
<br />
Upon exiting the app, the session will need to be terminated using the delete event. Sessions will automatically expire after 30 minutes of cloud inactivity.<br />
<br />
===Define URL Structure===<br />
Define the URL structure using your provided <code>[appid]</code> and a unique <code>[sessionID]</code>.<br />
<br />
<syntaxhighlight lang="javascript">http://sandbox.cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=[payload]</syntaxhighlight><br />
<br />
===Configure Payload===<br />
<br />
All Cloud API requests must contain the following payload data:<br />
<br />
*''devInfo'': device and app info<br />
*''metadata'': asset metadata<br />
*''event metadata'': type of event<br />
<br />
The payload can be passed through key-values using the Nielsen reserved keys. The specific keys and descriptions are highlighted in the tables included in this section.<br />
<br />
'''Payload Example'''<br />
<br />
The example below should be referenced when following the steps for configuring the request payload.<br />
<br />
<syntaxhighlight lang="javascript"><br />
// 3.1 Configure Payload: devInfo <br />
payload = {<br />
"devInfo": {<br />
"devId": "AD-ID", <br />
"apn": "AppName",<br />
"apv": "1.0",<br />
"uoo": "false"<br />
},<br />
<br />
// 3.2 Configure Payload: metadata<br />
"metadata": {<br />
"static": {}, // object for measuring static content<br />
"content": { // object for measuring video content<br />
"type": "content", // "content" for video<br />
"assetid": "VIDEO-ID123", // unique ID for video<br />
"isfullepisode": "y", // full episode flag<br />
"program": "Program Name", // program name<br />
"title": "Episode Title S3 - EP1", // episode name<br />
"length": "1800", // content duration in seconds<br />
"segB": "Custom Segment B", // custom segment<br />
"segC": "Custom Segment C", // custom segment<br />
"crossId1": "Standard Episode ID", // episode ID<br />
"crossId2": "Content Originator ID", // content orginator (required for distributors)<br />
"airdate": "20161013 20:00:00", // airdate<br />
"adloadtype": "2" //ad load flag<br />
"hasAds": "1", // content contains ads = 1 / no ads = 0<br />
"progen": "CV" // program genre abbreviation<br />
},<br />
"ad": {<br />
"type": "preroll", // type of ad<br />
"assetid": "AD-ID123" // unique ID for ad<br />
}<br />
},<br />
<br />
// 3.3 Configure Payload: events<br />
"event": "playhead", //event name<br />
"position": "300", // position in seconds<br />
"type": "content", //"content" or "ad"<br />
"utc": "1456448742000" //unix timestamp in milliseconds <br />
}<br />
</syntaxhighlight><br />
<br />
=====Configure Payload: devInfo=====<br />
An object <code>"devInfo"</code> will need to be created to capture App and Device information.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| devId || unique ID to identify user (e.g. Advertising ID, Roku Device ID) || custom || Yes<br />
|-<br />
| apn || app name || custom || Yes<br />
|-<br />
| apv || app build version || custom || Yes<br />
|-<br />
| uoo || device opt-out status || <code>"true"</code> or <code>"false"</code> || Yes<br />
|-<br />
|}<br />
<br />
'''Example devInfo Object'''<br />
<syntaxhighlight lang="javascript"><br />
// create devInfo object<br />
"devInfo": {<br />
"devId": "AD-ID",<br />
"apn": "AppName",<br />
"apv": "1.0",<br />
"uoo": "false"<br />
},<br />
</syntaxhighlight><br />
<br />
==== 3.2 Configure Payload: metadata ====<br />
Asset metadata can be passed through <code>"metadata"</code>. There are two asset types: <code>"content"</code> for video and <code>"ad"</code> for ads. The metadata received for each asset is used for classification and reporting.<br />
<br />
You will need to set up <code>"metadata"</code> objects for <code>"content"</code> and <code>"ad"</code> with the required Nielsen keys as shown in the sample code below.<br />
<br />
===== Content Metadata =====<br />
Content metadata should remain constant throughout the entirety of an episode/clip including when ads play.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| type || type of asset || <code>"content"</code> || Yes<br />
|-<br />
| assetid || unique ID assigned to asset || custom || Yes<br />
|-<br />
| program ||name of program (25 character limit) || custom || Yes<br />
|-<br />
| title ||name of program (25 character limit) || custom || Yes<br />
|-<br />
| length || length of content in seconds || <code>seconds</code> (86400 for live stream) || Yes<br />
|-<br />
| segB || custom segment B || custom || <br />
|-<br />
| segC || custom segment C || custom || <br />
|-<br />
| airdate || the airdate in the linear TV || YYYYMMDD HH24:MI:SS || Yes<br />
|-<br />
| isfullepisode || full episode flag || <code>"y"</code>- full episode, <code>"n"</code>- non full episode || Yes<br />
|-<br />
| crossId1 || standard episode ID || custom || Yes<br />
|-<br />
| crossId2 || content originator (only required for distributors) || Nielsen ||<br />
|-<br />
| adloadtype || type of ad load:<br />
<code>"1"</code> Linear – matches TV ad load<br />
<br />
<code>"2"</code> Dynamic – Dynamic Ad Insertion (DAI)<br />
|| <code>"2"</code> - DCR measures content with dynamic ads || Yes<br />
|-<br />
| hasAds || ads indicator<br />
<code>"1"</code>: ads included<br />
<br />
<code>"0"</code>: ads not included<br />
|| <code>"1"</code> or <code>"0"</code> || Yes<br />
|-<br />
| progen || program genre abbreviation - see [[DCR OTT Genre List]] for accepted values || <code>"CV"</code> for Comedy Variety || Yes<br />
|}<br />
<br />
<br />
'''Example Content Object'''<br />
<syntaxhighlight lang='json'>// create content object<br />
"content": {<br />
"type": "content",<br />
"assetid": "VIDEO-ID123",<br />
"isfullepisode": "y",<br />
"program": "Program Name",<br />
"title": "Episode Title S3 - EP1",<br />
"length": "1800",<br />
"segB": "Custom Segment B",<br />
"segC": "Custom Segment C",<br />
"crossId1": "Standard Episode ID",<br />
"crossId2": "Content Originator ID",<br />
"airdate": "20161013 20:00:00",<br />
"adloadtype": "2",<br />
"hasAds": "1", <br />
"progen": "CV"<br />
}</syntaxhighlight><br />
<br />
===== Ad Metadata =====<br />
The ad metadata should be passed for each individual ad.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| type || type of ad || <code>"preroll"</code>, <code>"midroll"</code>, or <code>"postroll"</code> || Yes<br />
|-<br />
| assetid || unique ID assigned to ad || custom || Yes<br />
|}<br />
<br />
===== Example Ad Object =====<br />
<syntaxhighlight lang="javascript"><br />
// create ad object<br />
"ad": {<br />
"type": "preroll",<br />
"assetid": "AD-ID123"<br />
}<br />
</syntaxhighlight><br />
<br />
=== Configure Payload: Events ===<br />
<br />
The last part of the payload is for enabling events so content is measured correctly when viewed. The events and required parameters are included below.<br />
<br />
==== Event Types ====<br />
<br />
The available events are:<br />
{| class="wikitable"<br />
|-<br />
! Event !! Description<br />
|-<br />
| <code>"playhead"</code> || Playhead position in seconds. Must be passed as a whole number every 10 seconds. The final playhead position should also be sent before an asset has changed to properly capture full duration. Playhead is used to handle pause and scrubbing. When content is paused, stop passing playhead position.<br />
|-<br />
| <code>"complete"</code> || Complete event must be sent when the content has completed full playback.Before calling the complete event, a final playhead update with the final position is required to be sent to receive full duration credit.<br />
|-<br />
| <code>"delete"</code> || The delete event must be sent when the session is completed, or terminated. After 30 minutes of inactivity, the session will expire. All creditable duration will be summarized for all asset types when delete occurs (content and ads).<br />
|}<br />
<br />
===== Event Parameters =====<br />
<br />
The following parameters need to be passed when calling events:<br />
<br />
{| class="wikitable"<br />
|-<br />
! Parameter !! Description !! Value !! Required<br />
|-<br />
| <code>"event"</code> || event type || <code>"playhead"</code>, <code>"complete"</code>, or <code>"delete"</code> || Yes<br />
|-<br />
| <code>"position"</code> || creditable position || playhead position in seconds or UTC timestamp in seconds for livestream || Yes<br />
|-<br />
| <code>"type"</code> || asset type || <code>"content"</code>, <code>"ad"</code> || Yes<br />
|-<br />
| <code>"utc"</code> || Unix timestamp in milliseconds. Must be passed every 10 seconds. || <code>"1472760000000"</code> || Yes<br />
|}<br />
<br />
===== Example Event =====<br />
You can call events by passing values in the required parameters:<br />
<br />
<syntaxhighlight lang="javascript"><br />
"devInfo": [deviceInfo],<br />
"metadata": {<br />
"static": [static metadata],<br />
"content": [content metadata],<br />
"ad": [ad metadata]<br />
},<br />
// Event Parameters<br />
"event": [event], // event name<br />
"position": [playheadPosition], //position in seconds<br />
"type": [asset type], // values are "content" or "ad"<br />
"utc": "1472760000000" //unix timestamp in milliseconds<br />
}<br />
</syntaxhighlight><br />
<br />
'''Note:''' The full payload including "devInfo" and "metadata" must be populated in each event request.<br />
<br />
===== Sample Event Lifecycle =====<br />
The sample event lifecycle can be used as a reference for identifying the order for calling events and values to pass.<br />
<br />
<syntaxhighlight lang="javascript"><br />
// Start of Session: session ID created when App is opened<br />
<br />
// Preroll<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
// Content<br />
Content Playhead {"event": "playhead", "position": "0", "type": "content", "utc": "1472760000000"} <br />
<br />
// Midroll<br />
Midroll Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
// Content resumes at 15 minutes<br />
Content Playhead {"event": "playhead", "position": "900", "type": "content", "utc": "1472760000000"} <br />
<br />
// Content completes at 30 minutes<br />
Complete {"event": "complete", "position": "1800", "type": "content", "utc": "1472760000000"} <br />
<br />
// Postroll<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
//End of Session: The delete event should be called when the App is exited. The values for position and type not required to be passed.<br />
Delete { "event": "delete", "position": "", "type": "", "utc": "1472760000000"} <br />
</syntaxhighlight><br />
<br />
<br />
'''Sample Event Lifecycle - Detailed Storyline'''<br />
This detailed event sequence provides additional insight for the correct events to call when handling certain playback scenarios.<br />
<syntaxhighlight lang='javascript'>// SESSION STARTS<br />
// Start of Session: session ID created when App is opened<br />
<br />
// PREROLL<br />
// Preroll Start - Start each Ad with a position of "0", resetting to '0' for each Ad, and Ad break.<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
// Preroll Stop - End each Ad with the final position of the Ad.<br />
Ad Playhead {"event": "playhead", "position": "15", "type": "ad", "utc": "1472761500000"}<br />
<br />
// CONTENT <br />
// Content Start - Start new content streams with a position of "0" incrementing the position every 10 seconds.<br />
Content Playhead {"event": "playhead", "position": "0", "type": "content", "utc": "1472761500000"} <br />
<br />
// Content Stop Before Ad Break - Send a playhead update including the current content positon before an Ad break.<br />
Content Playhead {"event": "playhead", "position": "299", "type": "content", "utc": "1472787400000"}<br />
<br />
// MIDROLL<br />
// Midroll Start - Start each Ad with a position of "0", resetting to '0' for each Ad, and Ad break.<br />
Midroll Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472787500000"} <br />
<br />
// Midroll Stop - End each Ad with the final position of the Ad.<br />
Ad Playhead {"event": "playhead", "position": "60", "type": "ad", "utc": "1472793500000"}<br />
<br />
// CONTENT<br />
// Content resumes at 5 minutes - Send playhead update with the current resumed position, and begin incrimenting the positon every 10 seconds.<br />
Content Playhead {"event": "playhead", "position": "300", "type": "content", "utc": "1472799500000"} <br />
<br />
// Content completes at 10:12 - Make sure to send in the playhead event with the final content position before sending the complete event.<br />
Final Content Playhead {"event": "playhead", "position": "612", "type": "content", "utc": "1472830700000"} <br />
<br />
Complete {"event": "complete", "position": "612", "type": "content", "utc": "1472830800000"} <br />
<br />
// POSTROLL<br />
// Postroll Start - Start each Ad with a position of "0", resetting to '0' for each Ad, and Ad break.<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472830900000"} <br />
<br />
// Postroll Stop - End each Ad with the final position of the Ad.<br />
Ad Playhead {"event": "playhead", "position": "45", "type": "ad", "utc": "1472835300000"}<br />
<br />
// SESSION ENDS<br />
<br />
//End of Session: The delete event should be called when the App is exited. The values for position and type not required to be passed.<br />
Delete { "event": "delete", "position": "", "type": "", "utc": "1472835400000"} </syntaxhighlight><br />
<br />
<br />
=====Handling Playhead=====<br />
Calling <code>"playhead"</code> is critical for accurate duration crediting. You can reference the below guidance to determine the correct playhead position to pass depending on the playback scenario.<br />
<br />
'''Playhead: General'''<br />
* Playhead position must start at 0 for each new asset, and be passed at least every 10 seconds.<br />
* Final postion must be sent at the end of content or an ad<br />
<br />
'''Playhead: Ads'''<br />
* The final position must be sent when switching from content to ad, or ad to content.<br />
* Each ad playhead position should be 0 at ad start.<br />
* For Ad Pods, playhead must be called, and reset to 0 for each individual ad. <br />
* The last content position before an Ad should be sent before switching to Ads.<br />
* When content has resumed following an ad break, the playhead position update must continue where the previous content segment left off.<br />
<br />
'''Playhead: User Actions'''<br />
* Upon user scrubbing, the current position must be sent before a user scrubs, and the new position should be sent where the user lands, and begin sending in the 10 second updates thereafter.<br />
* On pause, send the current position and then discontinue sending playhead event updates.<br />
* If a user exits a stream early, the last current position must be sent in a playhead update to receive accurate duration.<br />
<br />
===== Interruption Scenarios =====<br />
<br />
As part of configuring events, you will need to handle all possible interruption scenarios such as:<br />
<br />
*Wi-Fi OFF / ON<br />
*App going Background / Foreground (Video players only, not for Audio players)<br />
*App Crash or Exit<br />
<br />
When playback is interrupted, the app needs to send delete immediately.<br />
<br />
Once playback resumes, a new session will need to be created with a unique session ID. All of the required metadata and events will need to be sent.<br />
<br />
'''Note:''' The session will automatically timeout after 30 minutes of inactivity.<br />
<br />
=== Example Request ===<br />
<br />
Now that we walked through the Cloud API integration steps, your requests should have the following components: Session ID, App ID, and Payload. You can reference the example below when your reviewing your integration.<br />
<br />
<syntaxhighlight lang="javascript"><br />
// 1. Create Session ID<br />
sessionID = Date.now()+String(Math.random()*1000000 >> 0); // Must be GUID for each viewing session<br />
<br />
// 2. Define URL Structure with App ID and Session ID<br />
sessionURL = http://sandbox.cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=<br />
<br />
// 3. Configure Payload<br />
// 3.1 Configure Payload: devInfo <br />
payload = {<br />
"devInfo": {<br />
"devId": "AD-ID", <br />
"apn": "AppName",<br />
"apv": "1.0",<br />
"uoo": "false"<br />
},<br />
<br />
// 3.2 Configure Payload: metadata<br />
"metadata": {<br />
"static": {}, // object for measuring static content<br />
"content": { // object for measuring video content<br />
"type": "content", // "content" for video<br />
"assetid": "VIDEO-ID123", // unique ID for video<br />
"isfullepisode": "y", // full episode flag<br />
"program": "Program Name", // program name<br />
"title": "Episode Title S3 - EP1", // episode name<br />
"length": "1800", // content duration in seconds<br />
"segB": "Custom Segment B", // custom segment<br />
"segC": "Custom Segment C", // custom segment<br />
"crossId1": "Standard Episode ID", // episode ID<br />
"crossId2": "Content Originator ID", // content orginator (required for distributors)<br />
"airdate": "20161013 20:00:00", // airdate<br />
"adloadtype": "2", // ad load flag<br />
"hasAds": "1", // content contains ads = 1 / no ads = 0<br />
"progen": "CV" // program genre abbreviation<br />
},<br />
"ad": {<br />
"type": "preroll", // type of ad<br />
"assetid": "AD-ID123" // unique ID for ad<br />
}<br />
},<br />
<br />
// 3.3 Configure Payload: events<br />
"event": "playhead", //event name<br />
"position": "300", // position in seconds<br />
"type": "content", //"content" or "ad"<br />
"utc": "1456448742000" //unix timestamp in milliseconds <br />
}<br />
<br />
// Append payload to URL<br />
var image = new Image()<br />
image.onerror = function() {<br />
// wait and send again<br />
}<br />
(new Image).src = sessionURL+encodeURI(JSON.stringify(payload));<br />
</syntaxhighlight><br />
<br />
==== Enable Debug Logging ====<br />
<br />
Now that you have set up the Cloud API requests, you can enable debug logging to validate your integration. Enabling debug logging is required for Nielsen certification.<br />
<br />
==== GET Request ====<br />
<br />
Display GET Request to console using a name to identify each event (e.g. playhead).<br />
<br />
<syntaxhighlight lang="javascript"><br />
console.log("Event", image); <br />
</syntaxhighlight><br />
<br />
==== Payload ====<br />
<br />
Output payload to identify required metadata and events.<br />
<br />
<syntaxhighlight lang="javascript"><br />
console.log("Event Payload", payload); <br />
</syntaxhighlight><br />
<br />
==== HTTP Response Code ====<br />
<br />
Confirm request was completed by viewing HTTP response code.<br />
<br />
<syntaxhighlight lang="javascript"><br />
code = msg.GetResponseCode();<br />
console.log("Response Code", code); <br />
</syntaxhighlight><br />
<br />
You can reference the HTTP Response Code table when reviewing your requests:<br />
<br />
{| class="wikitable"<br />
|-<br />
! Status Code !! Status Text !! Description<br />
|-<br />
| <code>200</code> || OK || request received<br />
|-<br />
| <code>403</code> || Forbidden || invalid App ID<br />
|-<br />
| <code>404</code> || Not Found || JSON issue<br />
|}<br />
<br />
== Opt-Out ==<br />
Your app must provide a means for the user to Opt-Out, or Opt-In to Nielsen Measurement. The Opt-Out requirement can be fulfilled by creating an Opt-Out/Opt-In button, toggle switch, or slider within the app "Settings", or "About" section to allow the user selection. If you have the ability to render text within the Opt-Out screen, it is recommended to add the Privacy Information included below.<br />
<br />
[[File:Nielsen Opt-Out.png|link=]]<br />
<br />
You will need to store the User Opt-Out (uoo) status, so that it can be retrieved and populated in the <code>"devInfo"</code> metadata which will be sent in every Cloud API event (playhead, complete, & delete). <br />
<br />
*In addition, you will need to ensure that the devId field is a blank value if a user has elected to Opt-Out.<br />
<br />
*If the device offers "Limit Ad Tracking" or "Opt-Out" device settings, you must set uoo=true, and also ensure that the devId is set to a blank value if the user elects to enable this setting.<br />
<br />
{| class="wikitable"<br />
|-<br />
! uoo Key !! Description !! Values<br />
|-<br />
| uoo || Device is Opted-In to Nielsen Measurement (Recommended Default Setting) || <code>"false"</code><br />
|-<br />
| uoo || Device is Opted-Out of Nielsen Measurement || <code>"true"</code><br />
|}<br />
<br />
===== devInfo Opt-In JSON Payload Example =====<br />
<br />
<syntaxhighlight lang="javascript"><br />
"devInfo": {<br />
"apn": "Roku Sample App",<br />
"apv": "1",<br />
"devId": "7be25cf9-8c40-5cc2-871e-19bf41940288",<br />
"uoo": "false"<br />
},<br />
</syntaxhighlight><br />
<br />
===== devInfo Opt-Out JSON Payload Example =====<br />
<br />
<syntaxhighlight lang="javascript"><br />
"devInfo": {<br />
"apn": "Roku Sample App",<br />
"apv": "1",<br />
"devId": "", //devId must be blank when a user elects to Opt-Out.<br />
"uoo": "true"<br />
},<br />
</syntaxhighlight><br />
<br />
===== Privacy Information Template To Include In Opt-Out Screen =====<br />
<br />
<blockquote><br />
'''''ABOUT NIELSEN MEASUREMENT'''''<br />
<br />
Television and the way we watch it have come a long way since Nielsen began measuring TV audiences in 1950. Today, the ability to watch our favorite shows at any time and on multiple devices amplifies the need for exceptionally adept and flexible audience measurement capabilities.<br />
<br />
Consumers are changing with the times, and the same goes for us. As technology continues to evolve and media companies try new ways to attract viewers, understanding what consumers are watching — and what they're watching on — is more important than ever. Today, viewing video is a personal and mobile experience — anytime and anywhere. Our capabilities provide relevant metrics that are necessary to inform successful marketing and programming and drive continued growth. As a global information and measurement leader, we are committed to protecting the privacy and security of the data we collect, process and use. While our digital measurement products are not used to identify you in any way, they help us and our clients measure and analyze how consumers engage with media across online, mobile and emerging technologies, and offer insights into consumer behavior.<br />
<br />
'''''YOUR CHOICES'''''<br />
<br />
Nielsen believes that you should have a choice about whether to contribute to our research and insights. To opt out, or opt into Nielsen measurement please toggle your "Limit Ad Tracking" (or similar setting) on your device. If you have this app on more than one device, you will need to opt out of this app on each device. To learn more about our digital measurement products and your choices in regard to them, please visit http://www.nielsen.com/digitalprivacy.<br />
<br />
'''''Disclosure Template'''''<br />
<br />
Additionally, you must update the App description information from the App Distribution Store with the Nielsen Measurement disclosure below:<br />
<br />
This app features Nielsen's proprietary measurement software which will allow you to contribute to market research, like Nielsen's TV Ratings. Please see http://www.nielsen.com/digitalprivacy for more information.<br />
</blockquote><br />
<br />
== Testing ==<br />
Before providing an app build to Nielsen for testing, it is important to run validation checks once you have enabled debug logging.<br />
<br />
=== Payload Validation ===<br />
<br />
Ensure that all of the required payload data is populating while testing several videos. The following areas are critical to measurement:<br />
*devInfo<br />
*Asset metadata for both content, and ads<br />
*Events<br />
*Opt-Out status<br />
<br />
=== Player Events ===<br />
Review event calls:<br />
<br />
==== playhead ====<br />
*Playhead position updates every 10 seconds starting at position '0' for each new asset.<br />
*Final playhead position is sent on content, or ad before switching between assets.<br />
*Content metadata remains constant throughout an episode, or clip play.<br />
*Ad metadata is populated appropriately for each individual ad.<br />
*Playhead position update resumes for content after an ad break, and resets to 0 for each individual ad.<br />
*For scrubbing, last current position should be sent while scrubbing occurs, and the new position should also be sent where the user scrubs to.<br />
*Exiting a stream early should execute the last current position in a playhead update to receive accurate duration.<br />
*Upon pause, the current position should be sent, and playhead updates should stop incrementing until resume play occurs.<br />
<br />
==== complete ====<br />
*Check that the complete event executes upon content complete after the final playhead update is sent<br />
*Do not execute the complete event for ads<br />
<br />
==== delete ====<br />
*Check to see that the delete event occurs upon app exit<br />
<br />
==== GET Request Format ====<br />
*Ensure that the event payloads are formatted in JSON<br />
*Check to see that each of the Cloud API GET requests are properly encoded<br />
<br />
==== HTTP Response ====<br />
*Make sure that each of the Cloud API Get requests are received by the Nielsen Cloud API properly through use of the HTTP Response Code outputs enabled in console.<br />
<br />
==== Opt-Out ====<br />
*Test the "uoo" key gets populated accurately for both Opt-In and Opt-Out selections by validating the Cloud API events called after the user Opt-Out/Opt-In selection.<br />
*Test that the devId field is populated with a blank value if a user has elected to Opt-Out. For example: "devId": "",<br />
*If the device supports "Limit Ad Tracking" or has device "Opt-Out" settings, test that uoo=true, and that devId is set to a blank value if enabled in the device settings.<br />
<br />
== Go Live ==<br />
After your integration has been certified, you will need to: Change Endpoint and Disable Logging.<br />
<br />
'''Change Endpoint:''' You will need to update to the production endpoint:<br />
<br />
*Testing: <code>http://sandbox.cloudapi.imrworldwide.com/nmapi/v2/</code><br />
*Production: <code>https://cloudapi.imrworldwide.com/nmapi/v2/</code><br />
<br />
Your production URL structure should now be:<br />
<code>https://cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=[payload]</code><br />
<br />
'''Disable Logging:''' You can now disable debug logging</div>AlexGutierrezhttps://engineeringportal.nielsen.com//w/index.php?title=Digital_Measurement_Metadata&diff=2023Digital Measurement Metadata2017-11-08T19:14:28Z<p>AlexGutierrez: </p>
<hr />
<div>{{Breadcrumb|}} {{Breadcrumb|Digital}} {{Breadcrumb|DCR & DTVR}} {{CurrentBreadcrumb}}<br />
[[Category:Digital]]<br />
<br />
The metadata received for each asset is used for classification and reporting. There are reserved Nielsen keys for collecting the required metadata.<br />
<br />
=== Reserved Keys ===<br />
Content and Ad Metadata can be passed through key-values using the Nielsen reserved keys. The last column in the table below indicates metadata parameters that will be displayed in reporting.<br />
<br />
==== Content Metadata ====<br />
{| class="wikitable"<br />
|-<br />
! Key<br />
! Description<br />
! style="width: 14%;" | Values<br />
! style="width: 12%;" | Required for Audio/Video?<br />
! style="width: 12%;" | Required for Static (page)?<br />
! Reported for<br />
|-<br />
| clientid || Brand value is automatically populated through the App ID provided. The value passed here will override the default value. (e.g. Multiple brands in App) || custom || Optional || Optional || Audio / Video and Static Measurement<br />
|-<br />
| vcid || VCID value is automatically populated through the App ID provided. This value passed here will override the default value. (e.g. Multiple Sub-brands in App) || custom || Optional || Optional || Audio / Video and Static Measurement<br />
|-<br />
| type ||<br />
Type of measurement:<br />
*audio / video – "content"<br />
*page – "static"<br />
*ad – "preroll", "midroll" or "postroll"<br />
|| "content", "static" || Mandatory || Mandatory || <br />
|-<br />
| assetid || ID assigned to content. Must be unique across all content at the episode level. || custom || Mandatory || Not Required || <br />
|-<br />
| section || Section of site || custom || Not Required || Mandatory || <br />
|-<br />
| isfullepisode || Full episode flag ||<br />
*"y" or "lf" - full episode<br />
*"n" or "sf" - short form<br />
|| Mandatory || Not Required ||<br />
|-<br />
| program || Program name || custom || Mandatory || Not Required || Audio / Video Measurement<br />
|-<br />
| title || Episode title || custom || Mandatory || Not Required || Audio / Video Measurement<br />
|-<br />
| length || Length of content in seconds for VOD. 86400 may be used for Live content if the exact content length is not known. || custom || Mandatory || Not Required ||<br />
|-<br />
| segA || Segment A (this is not available for audio / video Measurement reporting as the episode title is reported) || custom || Not Required || Not Required || Static Measurement<br />
|-<br />
| segB || Segment B || custom || Not Required || Not Required || Audio / Video and Static Measurement<br />
|-<br />
| segC || Segment C || custom || Not Required || Not Required || Audio / Video and Static Measurement<br />
|-<br />
| crossId1 || Standard episode ID. Gracenote/TMS ID should be used when available. Must be unique per episode. || custom || Optional || Not Required ||<br />
|-<br />
| crossId2 || Content originator (required only for distributors) || custom || Optional || Not Required ||<br />
|-<br />
| airdate || Original (local) air date and time (Eastern Time for US) || YYYYMMDD HH24:MI:SS || Mandatory || Not Required ||<br />
|-<br />
| pipmode ||<br />
Current state of picture-in-picture (PIP) mode on device.<br />
*"true" if audio / video measurement is displayed in PIP mode<br />
*"false" if audio / video measurement is displayed in regular mode<br />
|| "true", "false" || Optional || Not Required || Audio / Video Measurement<br />
|-<br />
| adloadtype ||<br />
Type of ad load:<br />
#Linear - matches TV ad load<br />
#Dynamic - Dynamic Ad Insertion (DAI)<br />
|| "1" - Linear<br />
"2" - Dynamic<br />
|| Mandatory || Not Required ||<br />
|-<br />
| progen || Genre (required only for OTT). See [[DCR OTT Genre List]] for acceptable values. || e.g. DD for Daytime Drama || Required for OTT || Not Required<br />
|}<br />
<br />
'''clientid & subbrand (vcid)'''<br />
By default, clientid and subbrand are setup in Nielsen backend configuration to capture brand and sub-brand information. The fields get populated from backend for a registered client appid. But if an app contains multiple brands and sub-brands and client is willing to give credit to another brand or sub-brand then :<br />
*Client app developer can override the clientid and subbrand (brand and sub-brand info.) in [[loadMetadata]] call to get a proper measurement for a desired brand and sub-brand.<br />
**If no clientid and subbrand are specified in CMS of a content and an ad, default values reported in the Configuration will be reported.<br />
**If clientid and subbrand are specified in CMS of the content or ad, the most recent clientid and subbrand will be reported in subsequent pings. To report with a different clientid and subbrand, include the new values for the keys in their subsequent [[loadMetadata]] call.<br />
'''Picture-in-picture (PIP) mode'''<br />
Once the app detects PIP mode, call [[loadMetadata]] with the same set of assetids, values and with one additional parameter "pipmode": "true". Once PIP mode is disabled in the device, call [[loadMetadata]] again with "pipmode":"false".<br />
<br />
==== Custom Variables Extension ====<br />
Contact Nielsen Technical Account Manager (TAM) to configure any custom variables, as needed for implementing the application.<br />
<br />
See [[Custom Variables Extension]] for more information.<br />
<br />
==== Advertisement Metadata ====<br />
{| class="wikitable"<br />
|-<br />
! Key !! Description !! Values !! Required for Audio / Video? !! Required for Static (page)<br />
|-<br />
| type || Type of ad || "preroll"<br />
"midroll"<br />
"postroll" <br />
|| Mandatory || Not Required<br />
|-<br />
| assetid || An ID assigned to the advertisement. Can be an internal ID, an ID provided by the ad server, or a randomly generated ID. Must be unique across all ads || custom || Mandatory || Not Required<br />
|-<br />
| title || Title of the advertisement || "MyAdName" || Mandatory || Not Required<br />
|}<br />
<br />
=== Passing Metadata ===<br />
The required metadata can be passed as key values through the loadMetadata method. The sample code below shows a metadata objects for various assets.<br />
'''Audio / Video Measurement Asset'''<br />
Type should be "content"<br />
<syntaxhighlight lang="json"> {<br />
"type": "content",<br />
"assetName": "myassetName",<br />
"length": "300",<br />
"title": "myTitle",<br />
"program": "myProgram",<br />
"censuscategory": "myCensusCategory",<br />
"assetid": "myAssetId",<br />
"channelName": "myChannel",<br />
"adloadtype": "2",<br />
"segB": "segmentB",<br />
"segC": "segmentC",<br />
"isfullepisode":"y",<br />
"crossId1": "Reference11",<br />
"crossId2": "Reference22",<br />
"airdate": "20161013 20:00:00"<br />
}</syntaxhighlight><br />
<br />
'''Static (Page) Measurement Asset'''<br />
Type should be "static"<br />
<syntaxhighlight lang="json"> {<br />
"type": "static",<br />
"assetid": "static123",<br />
"assetName": "Page-Asset",<br />
"section": "siteSection",<br />
"segA": "segmentA",<br />
"segB": "segmentB",<br />
"segC": "segmentC"<br />
}</syntaxhighlight><br />
<br />
'''For any of the ad types'''<br />
<syntaxhighlight lang="json"> {<br />
"type": "midroll",<br />
"length": "30",<br />
"assetid": "myMidrollAssetId",<br />
"adloadtype": "2",<br />
"tv": "true",<br />
"dataSrc": "cms"<br />
}</syntaxhighlight><br />
<blockquote>'''Note''': In case the individual ad details are not available, send ad pod (presence) details through the <code>loadMetadata</code> and playhead position through <code>setPlayheadPosition</code>.</blockquote><br />
<br />
'''loadMetadata'''<br />
The object can then be passed when calling [[loadMetadata]].<br />
<syntaxhighlight lang="java"> loadMetadata(jsonMetadataObject);</syntaxhighlight></div>AlexGutierrezhttps://engineeringportal.nielsen.com//w/index.php?title=DCR_Video_%26_Static_Roku_Cloud_API&diff=1881DCR Video & Static Roku Cloud API2017-10-30T22:09:55Z<p>AlexGutierrez: </p>
<hr />
<div>{{Breadcrumb|}} {{Breadcrumb|Digital}} {{Breadcrumb|DCR & DTVR}} {{CurrentBreadcrumb}}<br />
[[Category:Digital]]<br />
<br />
This guide shows you how to integrate the Nielsen Cloud API to enable Digital Content Ratings (DCR) measurement on your Roku Apps.<br />
*For other OTT Apps, please see [[DCR Video & Static Cloud API]]<br />
*For Mobile Apps, please see [[DCR Video & Static Mobile Cloud API]]<br />
<br />
==Prerequisites==<br />
To get started, you will need a Nielsen App ID. The App ID is a unique ID assigned to your app. This will be provided to you upon starting the integration.<br />
<br />
<syntaxhighlight lang="javascript">XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX</syntaxhighlight><br />
<br />
==Integration==<br />
We will cover the steps for constructing the Cloud API Calls.<br />
<br />
===Request Overview===<br />
<br />
====URL Structure====<br />
<br />
The Cloud API Calls are HTTP GET Requests with the URL structure:<br />
<br />
<syntaxhighlight lang="javascript">[endpoint]/[appid]/[sessionID]/a?b=[payload]</syntaxhighlight><br />
<br />
The URL includes the following components:<br />
<br />
*<code>[endpoint]</code>: location of data collection environment<br />
*<code>[appid]</code>: provided App ID<br />
*<code>[sessionID]</code>: unique value for each user session<br />
*<code>[payload]</code>: metadata and events<br />
<br />
====Endpoint====<br />
<br />
There are endpoints for testing and production:<br />
<br />
*Testing: <code>http://sandbox.cloudapi.imrworldwide.com/nmapi/v2/</code><br />
*Production: <code>https://cloudapi.imrworldwide.com/nmapi/v2/</code><br />
<br />
During testing, all calls should be pointed to the testing endpoint. Once your application has been certified, the URL should be updated to point to the Cloud API production endpoint.<br />
<br />
====URL Example====<br />
As you move through the integration steps, you can reference the below URL structure with the expanded payload:<br />
<br />
<syntaxhighlight lang="javascript"><br />
http://sandbox.cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=<br />
{<br />
"devInfo": [deviceInfo],<br />
"metadata": {<br />
"static": [static_metadata],<br />
"content": [content_metadata],<br />
"ad": [ad metadata]<br />
},<br />
"event": [event],<br />
"position": [playhead_position],<br />
"type": [asset type],<br />
"utc": [UTC]<br />
}<br />
</syntaxhighlight><br />
<br />
===Create Session ID===<br />
A unique Session ID must be created upon app launch and provided in the URL. This will allow measurement to occur for the entire duration that a user is within the app.<br />
<br />
The session ID must be passed with every request and must remain consistent throughout each individual session.<br />
<br />
===Example Roku Sample Code Provided Below:===<br />
<syntaxhighlight lang="javascript">sessionTime = createObject("roDateTime")</syntaxhighlight><br />
<br />
<syntaxhighlight lang="javascript">sessionID = (sessionTime.asSeconds()).tostr()+(sessionTime.getMilliseconds()).tostr()</syntaxhighlight><br />
<br />
Upon exiting the app, the session will need to be terminated using the delete event. Sessions will automatically expire after 30 minutes of cloud inactivity.<br />
<br />
===Define URL Structure===<br />
Define the URL structure using your provided <code>[appid]</code> and a unique <code>[sessionID]</code>.<br />
<br />
<syntaxhighlight lang="javascript">http://sandbox.cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=[payload]</syntaxhighlight><br />
<br />
===Configure Payload===<br />
<br />
All Cloud API requests must contain the following payload data:<br />
<br />
*''devInfo'': device and app info<br />
*''metadata'': asset metadata<br />
*''event metadata'': type of event<br />
<br />
The payload can be passed through key-values using the Nielsen reserved keys. The specific keys and descriptions are highlighted in the tables included in this section.<br />
<br />
'''Payload Example'''<br />
<br />
The example below should be referenced when following the steps for configuring the request payload.<br />
<br />
<syntaxhighlight lang="javascript"><br />
// 3.1 Configure Payload: devInfo <br />
payload = {<br />
"devInfo": {<br />
"apn": "AppName",<br />
"encdata": "encdata=v1.key.RokuAES256CBC00.iv3x2EV0BpHH9AbZK%2FnBWWRhZbj7pD%3D...",<br />
"apv": "1.0",<br />
"uoo": "false"<br />
},<br />
<br />
// 3.2 Configure Payload: metadata<br />
"metadata": {<br />
"static": {}, // object for measuring static content<br />
"content": { // object for measuring video content<br />
"type": "content", // "content" for video<br />
"assetid": "VIDEO-ID123", // unique ID for video<br />
"isfullepisode": "y", // full episode flag<br />
"program": "Program Name", // program name<br />
"title": "Episode Title S3 - EP1", // episode name<br />
"length": "1800", // content duration in seconds<br />
"segB": "Custom Segment B", // custom segment<br />
"segC": "Custom Segment C", // custom segment<br />
"crossId1": "Standard Episode ID", // episode ID<br />
"crossId2": "Content Originator ID", // content orginator (required for distributors)<br />
"airdate": "20161013 20:00:00", // airdate<br />
"adloadtype": "2", //ad load flag<br />
"hasAds": "1", // content contains ads = 1 / no ads = 0<br />
"progen": "CV" // program genre abbreviation<br />
},<br />
"ad": {<br />
"type": "preroll", // type of ad<br />
"assetid": "AD-ID123" // unique ID for ad<br />
}<br />
},<br />
<br />
// 3.3 Configure Payload: events<br />
"event": "playhead", //event name<br />
"position": "300", // position in seconds<br />
"type": "content", //"content" or "ad"<br />
"utc": "1456448742000" //unix timestamp in milliseconds <br />
}<br />
</syntaxhighlight><br />
<br />
=====Configure Payload: devInfo=====<br />
An object <code>"devInfo"</code> will need to be created to capture App and Device information.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| apn || app name || custom || Yes<br />
|-<br />
| apv || app build version || custom || Yes<br />
|-<br />
| uoo || device opt-out status || <code>"true"</code> or <code>"false"</code> || Yes<br />
|-<br />
| Example || Example || Example || Yes<br />
|}<br />
<br />
'''Example devInfo Object'''<br />
<syntaxhighlight lang="javascript"><br />
// create devInfo object<br />
"devInfo": {<br />
"encdata": "encdata=v1.key.RokuAES256CB...",<br />
"apn": "AppName",<br />
"apv": "1.0",<br />
"uoo": "false"<br />
},<br />
</syntaxhighlight><br />
==== 3.2 Configure Payload: metadata ====<br />
Asset metadata can be passed through <code>"metadata"</code>. There are two asset types: <code>"content"</code> for video and <code>"ad"</code> for ads. The metadata received for each asset is used for classification and reporting.<br />
<br />
You will need to set up <code>"metadata"</code> objects for <code>"content"</code> and <code>"ad"</code> with the required Nielsen keys as shown in the sample code below.<br />
<br />
===== Content Metadata =====<br />
Content metadata should remain constant throughout the entirety of an episode/clip including when ads play.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| type || type of asset || <code>"content"</code> || Yes<br />
|-<br />
| assetid || unique ID assigned to asset || custom || Yes<br />
|-<br />
| program ||name of program (25 character limit) || custom || Yes<br />
|-<br />
| title ||name of program (25 character limit) || custom || Yes<br />
|-<br />
| length || length of content in seconds || <code>seconds</code> (86400 for live stream) || Yes<br />
|-<br />
| segB || custom segment B || custom || <br />
|-<br />
| segC || custom segment C || custom || <br />
|-<br />
| airdate || the airdate in the linear TV || YYYYMMDD HH24:MI:SS || Yes<br />
|-<br />
| isfullepisode || full episode flag || <code>"y"</code>- full episode, <code>"n"</code>- non full episode || Yes<br />
|-<br />
| crossId1 || standard episode ID || custom || Yes<br />
|-<br />
| crossId2 || content originator (only required for distributors) || Nielsen ||<br />
|-<br />
| adloadtype || type of ad load:<br />
<code>"1"</code> Linear – matches TV ad load<br />
<br />
<code>"2"</code> Dynamic – Dynamic Ad Insertion (DAI)<br />
|| <code>"2"</code> - DCR measures content with dynamic ads || Yes<br />
|-<br />
| hasAds || ads indicator<br />
<code>"1"</code>: ads included<br />
<br />
<code>"0"</code>: ads not included<br />
|| <code>"1"</code> or <code>"0"</code> || Yes<br />
|-<br />
| progen || program genre abbreviation - see [[DCR OTT Genre List]] for accepted values || <code>"CV"</code> for Comedy Variety || Yes<br />
|}<br />
<br />
<br />
'''Example Content Object'''<br />
<syntaxhighlight lang='json'>// create content object<br />
"content": {<br />
"type": "content",<br />
"assetid": "VIDEO-ID123",<br />
"isfullepisode": "y",<br />
"program": "Program Name",<br />
"title": "Episode Title S3 - EP1",<br />
"length": "1800",<br />
"segB": "Custom Segment B",<br />
"segC": "Custom Segment C",<br />
"crossId1": "Standard Episode ID",<br />
"crossId2": "Content Originator ID",<br />
"airdate": "20161013 20:00:00",<br />
"adloadtype": "2",<br />
"hasAds": "1", <br />
"progen": "CV"<br />
}</syntaxhighlight><br />
<br />
===== Ad Metadata =====<br />
The ad metadata should be passed for each individual ad.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Keys !! Description !! Values !! Required<br />
|-<br />
| type || type of ad || <code>"preroll"</code>, <code>"midroll"</code>, or <code>"postroll"</code> || Yes<br />
|-<br />
| assetid || unique ID assigned to ad || custom || Yes<br />
|}<br />
<br />
===== Example Ad Object =====<br />
<syntaxhighlight lang="javascript"><br />
// create ad object<br />
"ad": {<br />
"type": "preroll",<br />
"assetid": "AD-ID123"<br />
}<br />
</syntaxhighlight><br />
<br />
=== Configure Payload: Events ===<br />
<br />
The last part of the payload is for enabling events so content is measured correctly when viewed. The events and required parameters are included below.<br />
<br />
==== Event Types ====<br />
<br />
The available events are:<br />
{| class="wikitable"<br />
|-<br />
! Event !! Description<br />
|-<br />
| <code>"playhead"</code> || Playhead position in seconds. Must be passed as a whole number every 10 seconds. The final playhead position should also be sent before an asset has changed to properly capture full duration. Playhead is used to handle pause and scrubbing. When content is paused, stop passing playhead position.<br />
|-<br />
| <code>"complete"</code> || The complete event must be sent when the content has completed full playback. Before calling the complete event, a final playhead update with the final position is required to be sent to receive full duration credit.<br />
|-<br />
| <code>"delete"</code> || The delete event must be sent when the session is completed, or terminated. After 30 minutes of inactivity, the session will expire. All creditable duration will be summarized for all asset types when delete occurs (content and ads).<br />
|}<br />
<br />
===== Event Parameters =====<br />
<br />
The following parameters need to be passed when calling events:<br />
<br />
{| class="wikitable"<br />
|-<br />
! Parameter !! Description !! Value !! Required<br />
|-<br />
| <code>"event"</code> || event type || <code>"playhead"</code>, <code>"complete"</code>, or <code>"delete"</code> || Yes<br />
|-<br />
| <code>"position"</code> || creditable position || playhead position in seconds or UTC timestamp in seconds for livestream || Yes<br />
|-<br />
| <code>"type"</code> || asset type || <code>"content"</code>, <code>"ad"</code> || Yes<br />
|-<br />
| <code>"utc"</code> || Unix timestamp in milliseconds. Must be passed every 10 seconds. || <code>"1472760000000"</code> || Yes<br />
|}<br />
<br />
===== Example Event =====<br />
You can call events by passing values in the required parameters:<br />
<br />
<syntaxhighlight lang="javascript"><br />
"devInfo": [deviceInfo],<br />
"metadata": {<br />
"static": [static metadata],<br />
"content": [content metadata],<br />
"ad": [ad metadata]<br />
},<br />
// Event Parameters<br />
"event": [event], // event name<br />
"position": [playheadPosition], //position in seconds<br />
"type": [asset type], // values are "content" or "ad"<br />
"utc": "1472760000000" //unix timestamp in milliseconds<br />
}<br />
</syntaxhighlight><br />
<br />
'''Note:''' The full payload including "devInfo" and "metadata" must be populated in each event request.<br />
<br />
===Example Image Request===<br />
<br />
In order to execute Cloud API calls, image requests will need to be established for each event.<br />
<br />
===Playhead Image Request Example Code===<br />
<br />
<syntaxhighlight lang="javascript"><br />
playheadRequest = CreateObject("roUrlTransfer")<br />
sendUrl = sessionUrl+playheadRequest.Escape(playheadPayload)<br />
playheadRequest.SetUrl(sendUrl)<br />
xport=CreateObject("RoMessagePort")<br />
playheadRequest.setport(xport)<br />
aa3 = {}<br />
aa3["Connection"] = "keep-alive"<br />
aa3["Content-type"] = "image/gif"<br />
playheadRequest.SetHeaders(aa3)<br />
playheadRequest.setRequest("GET")<br />
</syntaxhighlight><br />
<br />
===== Sample Event Lifecycle =====<br />
The sample event lifecycle can be used as a reference for identifying the order for calling events and values to pass.<br />
<br />
<syntaxhighlight lang="javascript"><br />
// Start of Session: session ID created when App is opened<br />
<br />
// Preroll<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
// Content<br />
Content Playhead {"event": "playhead", "position": "0", "type": "content", "utc": "1472760000000"} <br />
<br />
// Midroll<br />
Midroll Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
// Content resumes at 15 minutes<br />
Content Playhead {"event": "playhead", "position": "900", "type": "content", "utc": "1472760000000"} <br />
<br />
// Content completes at 30 minutes<br />
Complete {"event": "complete", "position": "1800", "type": "content", "utc": "1472760000000"} <br />
<br />
// Postroll<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
//End of Session: The delete event should be called when the App is exited. The values for position and type not required to be passed.<br />
Delete { "event": "delete", "position": "", "type": "", "utc": "1472760000000"} <br />
</syntaxhighlight><br />
<br />
<br />
'''Sample Event Lifecycle - Detailed Storyline'''<br />
This detailed event sequence provides additional insight for the correct events to call when handling certain playback scenarios.<br />
<syntaxhighlight lang='javascript'>// SESSION STARTS<br />
// Start of Session: session ID created when App is opened<br />
<br />
// PREROLL<br />
// Preroll Start - Start each Ad with a position of "0", resetting to '0' for each Ad, and Ad break.<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472760000000"} <br />
<br />
// Preroll Stop - End each Ad with the final position of the Ad.<br />
Ad Playhead {"event": "playhead", "position": "15", "type": "ad", "utc": "1472761500000"}<br />
<br />
// CONTENT <br />
// Content Start - Start new content streams with a position of "0" incrementing the position every 10 seconds.<br />
Content Playhead {"event": "playhead", "position": "0", "type": "content", "utc": "1472761500000"} <br />
<br />
// Content Stop Before Ad Break - Send a playhead update including the current content positon before an Ad break.<br />
Content Playhead {"event": "playhead", "position": "299", "type": "content", "utc": "1472787400000"}<br />
<br />
// MIDROLL<br />
// Midroll Start - Start each Ad with a position of "0", resetting to '0' for each Ad, and Ad break.<br />
Midroll Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472787500000"} <br />
<br />
// Midroll Stop - End each Ad with the final position of the Ad.<br />
Ad Playhead {"event": "playhead", "position": "60", "type": "ad", "utc": "1472793500000"}<br />
<br />
// CONTENT<br />
// Content resumes at 5 minutes - Send playhead update with the current resumed position, and begin incrimenting the positon every 10 seconds.<br />
Content Playhead {"event": "playhead", "position": "300", "type": "content", "utc": "1472799500000"} <br />
<br />
// Content completes at 10:12 - Make sure to send in the playhead event with the final content position before sending the complete event.<br />
Final Content Playhead {"event": "playhead", "position": "612", "type": "content", "utc": "1472830700000"} <br />
<br />
Complete {"event": "complete", "position": "612", "type": "content", "utc": "1472830800000"} <br />
<br />
// POSTROLL<br />
// Postroll Start - Start each Ad with a position of "0", resetting to '0' for each Ad, and Ad break.<br />
Ad Playhead {"event": "playhead", "position": "0", "type": "ad", "utc": "1472830900000"} <br />
<br />
// Postroll Stop - End each Ad with the final position of the Ad.<br />
Ad Playhead {"event": "playhead", "position": "45", "type": "ad", "utc": "1472835300000"}<br />
<br />
// SESSION ENDS<br />
<br />
//End of Session: The delete event should be called when the App is exited. The values for position and type not required to be passed.<br />
Delete { "event": "delete", "position": "", "type": "", "utc": "1472835400000"} </syntaxhighlight><br />
<br />
<br />
=====Handling Playhead=====<br />
Calling <code>"playhead"</code> is critical for accurate duration crediting. You can reference the below guidance to determine the correct playhead position to pass depending on the playback scenario.<br />
<br />
'''Playhead: General'''<br />
* Playhead position must start at 0 for each new asset, and be passed at least every 10 seconds.<br />
* Final postion must be sent at the end of content or an ad<br />
<br />
'''Playhead: Ads'''<br />
* The final position must be sent when switching from content to ad, or ad to content.<br />
* Each ad playhead position should be 0 at ad start.<br />
* For Ad Pods, playhead must be called, and reset to 0 for each individual ad. <br />
* The last content position before an Ad should be sent before switching to Ads.<br />
* When content has resumed following an ad break, the playhead position update must continue where the previous content segment left off.<br />
<br />
'''Playhead: User Actions'''<br />
* Upon user scrubbing, the current position must be sent before a user scrubs, and the new position should be sent where the user lands, and begin sending in the 10 second updates thereafter.<br />
* On pause, send the current position and then discontinue sending playhead event updates.<br />
* If a user exits a stream early, the last current position must be sent in a playhead update to receive accurate duration.<br />
<br />
===== Interruption Scenarios =====<br />
<br />
As part of configuring events, you will need to handle all possible interruption scenarios such as:<br />
<br />
*Wi-Fi OFF / ON<br />
*App going Background / Foreground (Video players only, not for Audio players)<br />
*App Crash or Exit<br />
<br />
When playback is interrupted, the app needs to send delete immediately.<br />
<br />
Once playback resumes, a new session will need to be created with a unique session ID. All of the required metadata and events will need to be sent.<br />
<br />
'''Note:''' The session will automatically timeout after 30 minutes of inactivity.<br />
<br />
=== Example Request ===<br />
<br />
Now that we walked through the Cloud API integration steps, your requests should have the following components: Session ID, App ID, and Payload. You can reference the example below when your reviewing your integration.<br />
<br />
<syntaxhighlight lang="javascript"><br />
// 1. Create Session ID<br />
sessionTime = createObject("roDateTime")<br />
sessionID = (sessionTime.asSeconds()).tostr()+(sessionTime.getMilliseconds()).tostr()<br />
<br />
// 2. Define URL Structure with App ID and Session ID<br />
sessionURL = http://sandbox.cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=<br />
<br />
// 3. Configure Payload<br />
// 3.1 Configure Payload: devInfo <br />
payload = {<br />
"devInfo": {<br />
"encdata": "encdata=v1.key.RokuAES256CB...",<br />
"apn": "AppName",<br />
"apv": "1.0",<br />
"uoo": "false"<br />
},<br />
<br />
// 3.2 Configure Payload: metadata<br />
"metadata": {<br />
"static": {}, // object for measuring static content<br />
"content": { // object for measuring video content<br />
"type": "content", // "content" for video<br />
"assetid": "VIDEO-ID123", // unique ID for video<br />
"isfullepisode": "y", // full episode flag<br />
"program": "Program Name", // program name<br />
"title": "Episode Title S3 - EP1", // episode name<br />
"length": "1800", // content duration in seconds<br />
"segB": "Custom Segment B", // custom segment<br />
"segC": "Custom Segment C", // custom segment<br />
"crossId1": "Standard Episode ID", // episode ID<br />
"crossId2": "Content Originator ID", // content orginator (required for distributors)<br />
"airdate": "20161013 20:00:00", // airdate<br />
"adloadtype": "2", // ad load flag<br />
"hasAds": "1", // content contains ads = 1 / no ads = 0<br />
"progen": "CV" // program genre abbreviation<br />
},<br />
"ad": {<br />
"type": "preroll", // type of ad<br />
"assetid": "AD-ID123" // unique ID for ad<br />
}<br />
},<br />
<br />
// 3.3 Configure Payload: events<br />
"event": "playhead", //event name<br />
"position": "300", // position in seconds<br />
"type": "content", //"content" or "ad"<br />
"utc": "1456448742000" //unix timestamp in milliseconds <br />
}<br />
<br />
// Append payload to URL<br />
sendUrl = sessionUrl.Escape(payload)<br />
</syntaxhighlight><br />
<br />
==== Enable Debug Logging ====<br />
<br />
Now that you have set up the Cloud API requests, you can enable debug logging to validate your integration. Enabling debug logging is required for Nielsen certification.<br />
<br />
==== GET Request ====<br />
<br />
Display GET Request to console using a name to identify each event (e.g. playhead).<br />
<br />
<syntaxhighlight lang="javascript"><br />
print "Playhead Event " + sendUrl<br />
</syntaxhighlight><br />
<br />
==== Payload ====<br />
<br />
Output payload to identify required metadata and events.<br />
<br />
<syntaxhighlight lang="javascript"><br />
print "Event Payload "+ playheadPayload + modSeq%.tostr()<br />
</syntaxhighlight><br />
<br />
==== HTTP Response Code ====<br />
<br />
Confirm request was completed by viewing HTTP response code.<br />
<br />
<syntaxhighlight lang="javascript"><br />
httpCode = GetResponseCode()<br />
print "Response Code " +httpCode.tostr()<br />
</syntaxhighlight><br />
<br />
You can reference the HTTP Response Code table when reviewing your requests:<br />
<br />
{| class="wikitable"<br />
|-<br />
! Status Code !! Status Text !! Description<br />
|-<br />
| <code>200</code> || OK || request received<br />
|-<br />
| <code>403</code> || Forbidden || invalid App ID<br />
|-<br />
| <code>404</code> || Not Found || JSON issue<br />
|}<br />
<br />
== Opt-Out ==<br />
<br />
Your app must provide a means for the user to Opt-Out, or Opt-In to Nielsen Measurement. This can be implemented in two steps:<br />
<br />
==== Step 1: Nielsen Privacy Policy & Roku Channel Disclosure ====<br />
<br />
In your application, you must display the Nielsen privacy policy, which instructs users on how to opt out and opt in to Nielsen measurements. This text is usually displayed in an application's "Settings" or "About" screens.<br />
<br />
<blockquote><br />
'''''ABOUT NIELSEN MEASUREMENT'''''<br />
<br />
Television and the way we watch it have come a long way since Nielsen began measuring TV audiences in 1950. Today, the ability to watch our favorite shows at any time and on multiple devices amplifies the need for exceptionally adept and flexible audience measurement capabilities.<br />
<br />
Consumers are changing with the times, and the same goes for us. As technology continues to evolve and media companies try new ways to attract viewers, understanding what consumers are watching — and what they're watching on — is more important than ever. Today, viewing video is a personal and mobile experience — anytime and anywhere. Our capabilities provide relevant metrics that are necessary to inform successful marketing and programming and drive continued growth. As a global information and measurement leader, we are committed to protecting the privacy and security of the data we collect, process and use. While our digital measurement products are not used to identify you in any way, they help us and our clients measure and analyze how consumers engage with media across online, mobile and emerging technologies, and offer insights into consumer behavior.<br />
<br />
'''''YOUR CHOICES'''''<br />
<br />
Nielsen believes that you should have a choice about whether to contribute to our research and insights. To opt out, or opt into Nielsen measurement please choose the appropriate "Limit Ad Tracking" setting on your device. If you have this app on more than one device, you will need to opt out of this app on each device. To learn more about our digital measurement products and your choices in regard to them, please visit http://www.nielsen.com/digitalprivacy.<br />
</blockquote><br />
<br />
In addition, on your Roku Channel description, you should add the following disclosure:<br />
<br />
<blockquote><br />
This app features Nielsen's proprietary measurement software which will allow you to contribute to market research, like Nielsen's TV Ratings. Please see http://www.nielsen.com/digitalprivacy for more information.<br />
</blockquote><br />
<br />
==== Step 2: Use "Limit Ad Tracking" setting to set User Opt Out ====<br />
<br />
Depending on the user's selection for the "Limit Ad Tracking" device setting, you should set the Cloud API <code>"uoo"</code> parameter accordingly. Roku provides an API called [https://sdkdocs.roku.com/display/sdkdoc/ifDeviceInfo#ifDeviceInfo-IsAdIdTrackingDisabled()asBoolean IsAdIdTrackingDisabled()] to check the user's limit ad tracking setting.<br />
<br />
{| class="wikitable"<br />
|-<br />
! uoo Key !! Description !! Values<br />
|-<br />
| uoo || Device is Opted-In to Nielsen Measurement || <code>"false"</code><br />
|-<br />
| uoo || Device is Opted-Out of Nielsen Measurement || <code>"true"</code><br />
|}<br />
<br />
The <code>"uoo"</code> parameter is located in the <code>"devInfo"</code> JSON object, which will be sent in every Cloud API event (playhead, complete, & delete).<br />
<br />
===== devInfo Opt-In JSON Payload Example =====<br />
<br />
<syntaxhighlight lang="javascript"><br />
"devInfo": {<br />
"apn": "Roku Sample App",<br />
"apv": "1",<br />
"encdata": "encdata=v1.key.RokuAES256CB...",<br />
"uoo": "false"<br />
},<br />
</syntaxhighlight><br />
<br />
===== devInfo Opt-Out JSON Payload Example =====<br />
<br />
<syntaxhighlight lang="javascript"><br />
"devInfo": {<br />
"apn": "Roku Sample App",<br />
"apv": "1",<br />
"encdata": "", //Encdata must be blank when a user elects to Opt-Out.<br />
"uoo": "true"<br />
},<br />
</syntaxhighlight><br />
<br />
Your application should check the Limit Ad Tracking setting on each app start, and/or periodically, to ensure the user's ad tracking selection is reflected in the Nielsen opt-out setting in your app.<br />
<br />
*In addition, you will need to ensure that the encdata field is populated with a blank value if a user has elected to Opt-Out.<br />
<br />
== Testing ==<br />
Before providing an app build to Nielsen for testing, it is important to run validation checks once you have enabled debug logging.<br />
<br />
=== Payload Validation ===<br />
<br />
Ensure that all of the required payload data is populating while testing several videos. The following areas are critical to measurement:<br />
*devInfo<br />
*Asset metadata for both content, and ads<br />
*Events<br />
*Opt-Out status<br />
<br />
=== Player Events ===<br />
Review event calls:<br />
<br />
==== playhead ====<br />
*Playhead position updates every 10 seconds starting at position '0' for each new asset.<br />
*Final playhead position is sent on content, or ad before switching between assets.<br />
*Content metadata remains constant throughout an episode, or clip play.<br />
*Ad metadata is populated appropriately for each individual ad.<br />
*Playhead position update resumes for content after an ad break, and resets to 0 for each individual ad.<br />
*For scrubbing, last current position should be sent while scrubbing occurs, and the new position should also be sent where the user scrubs to.<br />
*Exiting a stream early should execute the last current position in a playhead update to receive accurate duration.<br />
*Upon pause, the current position should be sent, and playhead updates should stop incrementing until resume play occurs.<br />
<br />
==== complete ====<br />
*Check that the complete event executes upon content complete after the final playhead update is sent.<br />
*Do not execute the complete event for ads<br />
<br />
==== delete ====<br />
*Check to see that the delete event occurs upon app exit<br />
<br />
==== GET Request Format ====<br />
*Ensure that the event payloads are formatted in JSON<br />
*Check to see that each of the Cloud API GET requests are properly encoded.<br />
<br />
==== HTTP Response ====<br />
*Make sure that each of the Cloud API Get requests are received by the Nielsen Cloud API properly through use of the HTTP Response Code outputs enabled in console.<br />
<br />
==== Opt-Out ====<br />
*Test the "uoo" key gets populated accurately for both Opt-In and Opt-Out selections based on the device's "Limit Ad Tracking" setting.<br />
*Test that the encdata field is populated with a blank value if a user has elected to Opt-Out. For Example: "encdata": "",<br />
<br />
== Go Live ==<br />
After your integration has been certified, you will need to: Change Endpoint and Disable Logging.<br />
<br />
'''Change Endpoint:''' You will need to update to the production endpoint:<br />
<br />
*Testing: <code>http://sandbox.cloudapi.imrworldwide.com/nmapi/v2/</code><br />
*Production: <code>https://cloudapi.imrworldwide.com/nmapi/v2/</code><br />
<br />
Your production URL structure should now be:<br />
<code>https://cloudapi.imrworldwide.com/nmapi/v2/[appid]/[sessionID]/a?b=[payload]</code><br />
<br />
'''Disable Logging:''' You can now disable debug logging.</div>AlexGutierrezhttps://engineeringportal.nielsen.com//w/index.php?title=Digital_Measurement_Metadata&diff=1839Digital Measurement Metadata2017-10-25T17:45:56Z<p>AlexGutierrez: </p>
<hr />
<div>{{Breadcrumb|}} {{Breadcrumb|Digital}} {{Breadcrumb|DCR & DTVR}} {{CurrentBreadcrumb}}<br />
[[Category:Digital]]<br />
<br />
The metadata received for each asset is used for classification and reporting. There are reserved Nielsen keys for collecting the required metadata.<br />
<br />
=== Reserved Keys ===<br />
Content and Ad Metadata can be passed through key-values using the Nielsen reserved keys. The last column in the table below indicates metadata parameters that will be displayed in reporting.<br />
<br />
==== Content Metadata ====<br />
{| class="wikitable"<br />
|-<br />
! Key<br />
! Description<br />
! style="width: 14%;" | Values<br />
! style="width: 12%;" | Required for Audio/Video?<br />
! style="width: 12%;" | Required for Static (page)?<br />
! Reported for<br />
|-<br />
| clientid || Brand value is automatically populated through the App ID provided. The value passed here will override the default value. (e.g. Multiple brands in App) || custom || Optional || Optional || Audio / Video and Static Measurement<br />
|-<br />
| vcid || VCID value is automatically populated through the App ID provided. This value passed here will override the default value. (e.g. Multiple Sub-brands in App) || custom || Optional || Optional || Audio / Video and Static Measurement<br />
|-<br />
| type ||<br />
Type of measurement:<br />
*audio / video – "content"<br />
*page – "static"<br />
*ad – "preroll", "midroll" or "postroll"<br />
|| "content", "static" || Mandatory || Mandatory || <br />
|-<br />
| assetid || ID assigned to content. Must be unique across all content at the episode level. || custom || Mandatory || Not Required || <br />
|-<br />
| section || Section of site || custom || Not Required || Mandatory || <br />
|-<br />
| isfullepisode || Full episode flag ||<br />
*"y" or "lf" - full episode<br />
*"n" or "sf" - short form<br />
|| Mandatory || Not Required ||<br />
|-<br />
| program || Program name || custom || Mandatory || Not Required || Audio / Video Measurement<br />
|-<br />
| title || Episode title || custom || Mandatory || Not Required || Audio / Video Measurement<br />
|-<br />
| length || Length of content in seconds for VOD. The estimated content length has to be passed for live content. || custom || Mandatory || Not Required ||<br />
|-<br />
| segA || Segment A (this is not available for audio / video Measurement reporting as the episode title is reported) || custom || Not Required || Not Required || Static Measurement<br />
|-<br />
| segB || Segment B || custom || Not Required || Not Required || Audio / Video and Static Measurement<br />
|-<br />
| segC || Segment C || custom || Not Required || Not Required || Audio / Video and Static Measurement<br />
|-<br />
| crossId1 || Standard episode ID. Gracenote/TMS ID should be used when available. Must be unique per episode. || custom || Optional || Not Required ||<br />
|-<br />
| crossId2 || Content originator (required only for distributors) || custom || Optional || Not Required ||<br />
|-<br />
| airdate || Original (local) air date and time (Eastern Time for US) || YYYYMMDD HH24:MI:SS || Mandatory || Not Required ||<br />
|-<br />
| pipmode ||<br />
Current state of picture-in-picture (PIP) mode on device.<br />
*"true" if audio / video measurement is displayed in PIP mode<br />
*"false" if audio / video measurement is displayed in regular mode<br />
|| "true", "false" || Optional || Not Required || Audio / Video Measurement<br />
|-<br />
| adloadtype ||<br />
Type of ad load:<br />
#Linear - matches TV ad load<br />
#Dynamic - Dynamic Ad Insertion (DAI)<br />
|| "1" - Linear<br />
"2" - Dynamic<br />
|| Mandatory || Not Required ||<br />
|-<br />
| progen || Genre (required only for OTT). See [[DCR OTT Genre List]] for acceptable values. || e.g. DD for Daytime Drama || Required for OTT || Not Required<br />
|}<br />
<br />
'''clientid & subbrand (vcid)'''<br />
By default, clientid and subbrand are setup in Nielsen backend configuration to capture brand and sub-brand information. The fields get populated from backend for a registered client appid. But if an app contains multiple brands and sub-brands and client is willing to give credit to another brand or sub-brand then :<br />
*Client app developer can override the clientid and subbrand (brand and sub-brand info.) in [[loadMetadata]] call to get a proper measurement for a desired brand and sub-brand.<br />
**If no clientid and subbrand are specified in CMS of a content and an ad, default values reported in the Configuration will be reported.<br />
**If clientid and subbrand are specified in CMS of the content or ad, the most recent clientid and subbrand will be reported in subsequent pings. To report with a different clientid and subbrand, include the new values for the keys in their subsequent [[loadMetadata]] call.<br />
'''Picture-in-picture (PIP) mode'''<br />
Once the app detects PIP mode, call [[loadMetadata]] with the same set of assetids, values and with one additional parameter "pipmode": "true". Once PIP mode is disabled in the device, call [[loadMetadata]] again with "pipmode":"false".<br />
<br />
==== Custom Variables Extension ====<br />
Contact Nielsen Technical Account Manager (TAM) to configure any custom variables, as needed for implementing the application.<br />
<br />
See [[Custom Variables Extension]] for more information.<br />
<br />
==== Advertisement Metadata ====<br />
{| class="wikitable"<br />
|-<br />
! Key !! Description !! Values !! Required for Audio / Video? !! Required for Static (page)<br />
|-<br />
| type || Type of ad || "preroll"<br />
"midroll"<br />
"postroll" <br />
|| Mandatory || Not Required<br />
|-<br />
| assetid || An ID assigned to the advertisement. Can be an internal ID, an ID provided by the ad server, or a randomly generated ID. Must be unique across all ads || custom || Mandatory || Not Required<br />
|-<br />
| title || Title of the advertisement || "MyAdName" || Mandatory || Not Required<br />
|}<br />
<br />
=== Passing Metadata ===<br />
The required metadata can be passed as key values through the loadMetadata method. The sample code below shows a metadata objects for various assets.<br />
'''Audio / Video Measurement Asset'''<br />
Type should be "content"<br />
<syntaxhighlight lang="json"> {<br />
"type": "content",<br />
"assetName": "myassetName",<br />
"length": "300",<br />
"title": "myTitle",<br />
"program": "myProgram",<br />
"censuscategory": "myCensusCategory",<br />
"assetid": "myAssetId",<br />
"channelName": "myChannel",<br />
"adloadtype": "2",<br />
"segB": "segmentB",<br />
"segC": "segmentC",<br />
"isfullepisode":"y",<br />
"crossId1": "Reference11",<br />
"crossId2": "Reference22",<br />
"airdate": "20161013 20:00:00"<br />
}</syntaxhighlight><br />
<br />
'''Static (Page) Measurement Asset'''<br />
Type should be "static"<br />
<syntaxhighlight lang="json"> {<br />
"type": "static",<br />
"assetid": "static123",<br />
"assetName": "Page-Asset",<br />
"section": "siteSection",<br />
"segA": "segmentA",<br />
"segB": "segmentB",<br />
"segC": "segmentC"<br />
}</syntaxhighlight><br />
<br />
'''For any of the ad types'''<br />
<syntaxhighlight lang="json"> {<br />
"type": "midroll",<br />
"length": "30",<br />
"assetid": "myMidrollAssetId",<br />
"adloadtype": "2",<br />
"tv": "true",<br />
"dataSrc": "cms"<br />
}</syntaxhighlight><br />
<blockquote>'''Note''': In case the individual ad details are not available, send ad pod (presence) details through the <code>loadMetadata</code> and playhead position through <code>setPlayheadPosition</code>.</blockquote><br />
<br />
'''loadMetadata'''<br />
The object can then be passed when calling [[loadMetadata]].<br />
<syntaxhighlight lang="java"> loadMetadata(jsonMetadataObject);</syntaxhighlight></div>AlexGutierrezhttps://engineeringportal.nielsen.com//w/index.php?title=DCR_Video_Android_SDK&diff=1838DCR Video Android SDK2017-10-25T16:47:19Z<p>AlexGutierrez: progen</p>
<hr />
<div>{{Breadcrumb|}} {{Breadcrumb|Digital}} {{Breadcrumb|DCR & DTVR}} {{CurrentBreadcrumb}}<br />
[[Category:Digital]]<br />
<br />
== Prerequisites ==<br />
To start using the App SDK, the following details are required:<br />
* '''App ID (appid):''' Unique ID assigned to the player/site and configured by product.<br />
* '''sfcode:''' Unique identifier for the environment that the SDK should point to.<br />
* '''Nielsen SDK''' and '''Sample Player''': A part of the downloaded package<br />
If you do not have any of these pre-requisites or if you have any questions, please contact our SDK sales support team.<br />
Refer to [[Digital Measurement Onboarding]] guide for more information on how to get a Nielsen App SDK and appid.<br />
<br />
== Import Library ==<br />
Refer to [[Android SDK API Reference#Setting Up Development Environment|Android SDK API Reference - Setting Up Development Environment]] for information on importing libraries.<br />
* The latest version of App SDK allows instantiating multiple instances of App SDK object and can be used simultaneously without any issues.<br />
<blockquote>Note: The latest version of App SDK contains only ''appsdk.jar'' file and does not feature any native shared libraries like ''libAppSdk.so''.</blockquote><br />
<br />
== Initialize SDK ==<br />
Initialize App SDK as soon as the application is launched. Refer to [[Android SDK API Reference#Initialization|Android SDK API Reference - Initialization]] for details on initializing an AppSDK object and the parameters required.<br />
<br />
== Configure API calls ==<br />
=== play ===<br />
Use [[play()]] to pass the channel descriptor information through channelName parameter when the user taps the '''Play''' button on the player.<br />
<br />
=== loadMetadata ===<br />
Use [[loadMetadata()]] to pass 'content' and 'ad' [[Digital Measurement Metadata]]. The CMS data must be passed as a JSON object.<br />
<syntaxhighlight lang="java"> loadMetadata(JSONObject jsonMetadata);</syntaxhighlight><br />
Refer to [[Digital Measurement Metadata]] section for the list of parameters to be passed in the JSON string.<br />
<blockquote>Note: The [[loadMetadata()]] call after the first [[play()]] call must have 'content' details ("type": "content"). This call should occur before any preroll ad starts playing.</blockquote><br />
<br />
=== setPlayheadPosition (Content) ===<br />
Use [[setPlayheadPosition()]] to pass the position of the playhead while the content is being played.<br />
<syntaxhighlight lang="java"> public AppSdk setPlayheadPosition(long position)</syntaxhighlight><br />
<br />
<br />
{| class="wikitable"<br />
|-<br />
! # !! Key !! Description !! Values !! Required? (Y/N) !! Example<br />
|-<br />
| 1 || Live** || UTC of the live content || Client-defined || Yes || Seconds since 1970<br />
|-<br />
| 2 || Video On Demand (VOD)** || Position taken from beginning of the content in seconds. || Client-defined || Yes || Current player position from beginning of the content.<br />
|}<br />
<nowiki>**</nowiki>Only one of these parameters is mandatory<br />
<blockquote>Note: If playhead is not available from audio / video measurement, allow the app to start a timer (1-5 seconds) and send playhead position to SDK based on the timer event. Once the actual playhead is available, let the app send the proper playhead position. This allows the SDK to calculate and provide a closer value for duration.</blockquote><br />
<br />
'''Buffering state'''<br />
* Do not supply playhead position while the content is being buffered.<br />
* If the content is in buffering state continuously for more than 30 seconds, call the [[stop | stop()]] API.<br />
<br />
'''Live Content'''<br />
<syntaxhighlight lang="java"> Calendar c = Calendar.getInstance();<br />
long pos = (c.getTimeInMillis()/ 1000);<br />
if (mAppSdk != null)<br />
{<br />
mAppSdk.setPlayheadPosition(pos);<br />
}</syntaxhighlight><br />
<br />
<br />
'''On-demand Content'''<br />
<syntaxhighlight lang="java"> long pos = mPlayer.videoPosition() / 1000;<br />
if (mAppSdk != null)<br />
{<br />
mAppSdk.setPlayheadPosition(pos);<br />
}</syntaxhighlight><br />
<br />
=== setPlayheadPosition (Ad) ===<br />
Use [[setPlayheadPosition()]] to pass the position of the playhead while the advertisement is being played.<br />
<syntaxhighlight lang="java"> public AppSdk setPlayheadPosition(long position)</syntaxhighlight><br />
<br />
{| class="wikitable"<br />
|-<br />
! Key !! Description !! Values !! Required? (Y/N) !! Example<br />
|-<br />
| Ad || Position taken from beginning of the ad in seconds || Client-defined || Yes || Seconds since start of the ad<br />
|}<br />
<br />
'''Ad Content'''<br />
<syntaxhighlight lang="java"> long pos = mAdPlayer.videoPosition() / 1000;<br />
if (mAppSdk != null)<br />
{<br />
mAppSdk.setPlayheadPosition(pos);<br />
}</syntaxhighlight><br />
<blockquote>Note: The playhead positions for ad and content should be maintained separately.</blockquote><br />
<br />
=== stop ===<br />
Call [[stop | stop()]] only at the end of ad and when the stream playback is stopped by a user or an interruption from another event. Common events are; network loss, power / standby, incoming call, alarm, etc. Call [[stop | stop()]] only when buffering continues for 30 seconds (not when it starts). Call [[loadMetadata()]] and [[setPlayheadPosition()]] when a previously stopped stream resumes. Call [[play | play()]] when starting playback of a new stream.<br />
<br />
=== end ===<br />
Call [[end()]] only at the end of playback.<br />
<br />
=== API Call sequence ===<br />
==== Use Case 1: Content has no Advertisements ====<br />
Call [[play()]] with channelName JSON as below.<br />
<syntaxhighlight lang="json">{<br />
"channelName": "TheMovieTitle"<br />
}</syntaxhighlight><br />
Call [[loadMetadata()]] with JSON metadata for content as below.<br />
<syntaxhighlight lang="json">{<br />
"type": "content",<br />
"assetid": "vid345-67483",<br />
"program": "ProgramName",<br />
"title": "Program S3, EP1",<br />
"length": "3600",<br />
"segB": "CustomSegmentValueB",<br />
"segC": "CustomSegmentValueC",<br />
"crossId1": "Reference11",<br />
"crossId2": "Reference22",<br />
"isfullepisode": "y",<br />
"airdate": "20161013 20:00:00",<br />
"adloadtype": "2",<br />
"progen":"GV"<br />
}</syntaxhighlight><br />
Call [[setPlayheadPosition()]] every one second until a pause / stop.<br />
Use the sample API sequence below as a reference to identify the specific events that need to be called during content playback without ads.<br />
{| class="wikitable"<br />
|-<br />
! Type !! Sample code !! Description<br />
|-<br />
| rowspan="2" | Start of stream || <code>mAppSdk.play(channelName); </code> || // channelName contains JSON metadata of channel/video name being played<br />
|-<br />
| <code>mAppSdk.loadMetadata(contentMetaDataObject);</code> || // contentMetadataObject contains the JSON metadata for the content being played<br />
|-<br />
| Content || <code>mAppSdk.setPlayheadPosition(playheadPosition);</code> || // position is position of the playhead while the content is being played<br />
|-<br />
| End of Stream || <code>mAppSdk.end();</code> || // Content playback is completed.<br />
|}<br />
<br />
==== Use Case 2: Content has Advertisements ====<br />
Call [[play()]] with channelName JSON as below.<br />
<syntaxhighlight lang="json">{<br />
"channelName": "TheMovieTitle"<br />
}</syntaxhighlight><br />
Call [[loadMetadata()]] with JSON metadata for ad as below.<br />
<syntaxhighlight lang="json">{<br />
"type": "preroll",<br />
"assetid": "ad=123"<br />
}</syntaxhighlight><br />
<blockquote>Note: In case the individual ad details are not available, send ad pod (presence) details through the [[loadMetadata]] and playhead position through [[playheadPosition]].</blockquote><br />
<br />
Call [[setPlayheadPosition()]] every one second until a pause / stop / another [[loadMetadata()]] is called. Playhead should be passed for the entire duration of ad pod, if the ad pod details are passed as part of [[loadMetadata()]].<br />
<br />
The sample API sequence can be used as a reference to identify the specific events that need to be called during content and ad playback.<br />
{| class="wikitable"<br />
|-<br />
! Type !! Sample code !! Description<br />
|-<br />
| rowspan="2" | Start of stream || <code>mAppSdk.play(channelName); </code> || // channelName contains JSON metadata of channel/video name being played<br />
|-<br />
| <code>mAppSdk.loadMetadata(contentMetaDataObject);</code> || // contentMetadataObject contains the JSON metadata for the content being played<br />
|-<br />
| rowspan="3" | Preroll || <code>mAppSdk.loadMetadata(prerollMetadataObject);</code> || // prerollMetadataObject contains the JSON metadata for the preroll ad<br />
|-<br />
| <code>mAppSdk.setPlayheadPosition(playheadPosition);</code> || // position is position of the playhead while the preroll ad is being played<br />
|-<br />
| <code>mAppSdk.stop();</code> || // Call stop after preroll occurs<br />
|-<br />
| rowspan="3" | Content || <code>mAppSdk.loadMetadata(contentMetaDataObject);</code> || // contentMetadataObject contains the JSON metadata for the content being played<br />
|-<br />
| <code>mAppSdk.setPlayheadPosition(playheadPosition);</code> || // position is position of the playhead while the content is being played<br />
|-<br />
| <code>mAppSdk.stop();</code> || // Call stop after the content is paused (ad starts)<br />
|-<br />
| rowspan="6" | Midroll || <code>mAppSdk.loadMetadata(midrollMetaDataObject);</code> || // midrollMetadataObject contains the JSON metadata for the midroll ad<br />
|-<br />
| <code>mAppSdk.setPlayheadPosition(playheadPosition);</code> || // position is position of the playhead while the midroll ad is being played<br />
|-<br />
| <code>mAppSdk.stop();</code> || // App moves to background(midroll pauses) <br />
|-<br />
| <code>mAppSdk.loadMetadata(midrollMetaDataObject);</code> || // App moves to foreground (midroll resumes) <br />
|-<br />
| <code>mAppSdk.setPlayheadPosition(playheadPosition);</code> || // playheadPosition is position of the playhead while the midroll ad is being played <br />
|-<br />
| <code>mAppSdk.stop();</code> || // Call stop after midroll occurs<br />
|-<br />
| rowspan="3" | Content (End of stream) || <code>mAppSdk.loadMetadata(contentMetaDataObject);</code> || // contentMetadataObject contains the JSON metadata for the content being played<br />
|-<br />
| <code>mAppSdk.setPlayheadPosition(playheadPosition);</code> || // position is position of the playhead while the content is being played<br />
|-<br />
| <code>mAppSdk.stop();</code> || // Always call stop irrespective of postroll is followed or not<br />
|-<br />
| End of Stream || <code>mAppSdk.end();</code> || // Call end() at the end of content<br />
|-<br />
| rowspan="3" | Postroll || <code>mAppSdk.loadMetadata(postrollMetaDataObject);</code> || // postrollMetadataObject contains the JSON metadata for the postroll ad<br />
|-<br />
| <code>mAppSdk.setPlayheadPosition(playheadPosition);</code> || // position is position of the playhead while the postroll ad is being played<br />
|-<br />
| <code>mAppSdk.stop();</code> || // Call stop after postroll occurs<br />
|}<br />
<br />
<blockquote>Note: Each Ad playhead should reset or begin from 0 at ad start. When content has resumed following an ad break, playhead position must continue from where previous content segment was left off.</blockquote><br />
<br />
== Handling Foreground and Background states of App ==<br />
There are two ways of handling the foreground and background states of the client application.<br />
* Let App SDK handle the app states information (foreground / background) and use it, as necessary. See [[#New_devices_.28Android_4.0_and_later_versions.29]] for more information.<br />
* Capture app states through the application and trigger the corresponding API ([[appInForeground()]] or [[appInBackground()]]) upon every change of state. This allows Nielsen App SDK to know the app state. See [[#Older_devices_.28Android_4.0_or_earlier_versions.29]] for more information.<br />
<br />
=== New devices (Android 4.0 and later versions) ===<br />
'''Add ''application'' tag to Manifest XML file'''<br />
When client's app supports only new devices (Android 4.0 and above) and the client has not implemented a custom Application class for some other purpose,<br />
* Add the following entry (application tag) into the Manifest XML file to use SDK's state detection feature.<br />
<syntaxhighlight lang="java"> <application android:name="com.nielsen.app.sdk.AppSdkApplication"></syntaxhighlight><br />
This is the custom Application class where the whole background detection implementation is done.<br />
<blockquote>Note: No new permissions are required to change the properties in Manifest XML file.</blockquote><br />
<br />
=== Older devices (Android 4.0 or earlier versions) ===<br />
Identify the change of state through the application and call the respective API ([[appInForeground()]] or [[appInBackground()]]) upon every change of state (foreground / background). The SDK will use this information to pass the app launch times, etc. to Collection facility.<br />
<syntaxhighlight lang="java"> AppLaunchMeasurementManager.appInForeground(getApplicationContext());<br />
AppLaunchMeasurementManager.appInBackground(getApplicationContext());</syntaxhighlight><br />
<br />
== Interruptions during playback ==<br />
As part of integrating Nielsen App SDK with the player application, the Audio / Video app developer needs to handle the following possible interruption scenarios:<br />
* Pause / Play<br />
* Network Loss (Wi-Fi / Airplane / Cellular)<br />
* Call Interrupt (SIM or Third party Skype / Hangout call)<br />
* Alarm Interrupt<br />
* Content Buffering<br />
* Device Lock / Unlock (Video players only, not for Audio players)<br />
* App going in the Background/Foreground (Video players only, not for Audio players)<br />
* Channel / Station Change Scenario<br />
* Unplugging of headphone<br />
In case of encountering one of the above interruptions, the player application needs to<br />
* Call [[stop()]] immediately (except when content is buffering) and withhold sending playhead position.<br />
* Start sending pings – [[loadMetadata()]] and [[setPlayheadPosition()]] for the new viewing session, once the playback resumes.<br />
Please see the [[Digital Measurement FAQ]] for more details<br />
<br />
== Nielsen Measurement Opt-Out Implementation ==<br />
As a global information and measurement leader, we are committed to protecting the privacy and security of the data we collect, process and use. Our digital measurement products are not used to identify the consumer in any way, but they help us and our clients measure and analyze how consumers engage with media across online, mobile and emerging technologies, and offer insights into consumer behavior.<br />
* When the app user wants to opt in or opt out of Nielsen measurement, a new dynamic page (with content string obtained from [[userOptOutURLString()]]) should be displayed.<br />
* Use [[getOptOutStatus()]] to retrieve the device's Opt-Out status.<br />
* This Opt-out page should be displayed in a webview (within the app) and not in any external browser.<br />
* Capture the user's selection in this page and pass it to the SDK through [[userOptOut()]] for Nielsen to save the user's preference.<br />
* For more details, refer to [[Android SDK API Reference#Android Opt-Out Implementation|Android SDK API Reference - Android Opt-Out Implementation]] and Nielsen Digital Privacy.<br />
<br />
== Pre-Certification Checklists ==<br />
After the application is ready to be sent for Nielsen Certification, please go through the Pre-Certification Checklist and ensure the app behaves as expected, before submitting to Nielsen.<br />
<br />
== Testing an Implementation - App ==<br />
See [[Digital Measurement Testing]].</div>AlexGutierrez