# Nielsen AppSdk Integration Guide

This guide provides step-by-step instructions for integrating the **Nielsen AppSdk** into your Android TV application. The Nielsen AppSdk is a library for measuring digital audience on Android CTV platforms. The SDK supports three measurement types:

- **DCR Video** - Digital Content Ratings for VOD and live streaming video content with playhead tracking
- **DCR Static** - For non-video content such as web views, articles, and informational screens
- **DTVR** - Digital TV Ratings for live TV streams with embedded Nielsen ID3 watermarks

---

## Table of Contents

1. [Add SDK Dependency](#step-1-add-sdk-dependency)
2. [Initialize AppSdk](#step-2-initialize-appsdk)
3. [Configure SDK Parameters](#step-3-configure-sdk-parameters)
4. [DCR Video - Start Measurement](#step-4-dcr-video---start-measurement)
5. [DCR Video - Playhead Tracking](#step-5-dcr-video---playhead-tracking)
6. [DCR Video - Stop/End](#step-6-dcr-video---stopend)
7. [DCR Static - Web Content](#step-7-dcr-static---web-content)
8. [DTVR - ID3 Tag Processing](#step-8-dtvr---id3-tag-processing)
9. [User Opt-Out](#step-9-user-opt-out)
10. [Lifecycle Management](#step-10-lifecycle-management)

---

## Step 1: Add SDK Dependency

Add the Nielsen AppSdk JAR maven dependency to your project and configure build.gradle.

### build.gradle (app level)

```gradle
dependencies {
    implementation 'com.nielsenappsdk.global:ad:10.2.0.0'
    implementation 'com.google.android.gms:play-services-ads-identifier:18.2.0'
}
```

### AndroidManifest.xml

```xml
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
```

> **Note:** Contact Nielsen to obtain the SDK JAR and your unique App ID.

---

## Step 2: Initialize AppSdk

Create a singleton manager and initialize the AppSdk when the app starts.

```kotlin
private var mAppSdk: AppSdk? = null

fun initializeAppSdk(context: Context, config: JSONObject) {
    mAppSdk = AppSdk(context, config, object : IAppNotifier {
        override fun onAppSdkEvent(timestamp: Long, code: Int, description: String?) {
            if (code == AppSdk.EVENT_STARTUP) {
                Log.d("Nielsen", "AppSdk initialized successfully")
            }
        }
    })
    
    if (mAppSdk?.isValid != true) {
        Log.e("Nielsen", "Failed to initialize AppSdk")
    }
}
```

---

## Step 3: Configure SDK Parameters

Build the JSON configuration with your Nielsen credentials.

```kotlin
val config = JSONObject()
    .put("appid", "YOUR_NIELSEN_APP_ID")    // Required: Nielsen-provided App ID
    .put("sfcode", "cert")                   // "cert" for testing, "us" for production
    .put("clientid", "your_client_id")       // Client identifier
    .put("vcid", "your_vcid")                // Video Census ID
    .put("prod", "vc,iag")                   // Product codes
    .put("category", "your_category")        // App category
    .put("nol_devDebug", "DEBUG")            // Remove in production
    .put("uid2", "unified_id_value")         // Optional: UID2 identifier
    .put("hem_sha256", "hashed_email")       // Optional: Hashed email
```

| Parameter | Required | Description |
|-----------|----------|-------------|
| `appid` | Yes | Nielsen-provided unique App ID |
| `sfcode` | Yes | "cert" for testing, "us" for production |
| `vcid` | No | Video Census ID for content identification |
| `clientid` | No | Client identifier for reporting |
| `nol_devDebug` | No | Set "DEBUG" for verbose logging |

---

## Step 4: DCR Video - Start Measurement

Call `play()` and `loadMetadata()` when video playback begins.

```kotlin
fun appStartMeteringVideo(video: Video) {
    // Build content metadata
    val metaData = JSONObject()
        .put("type", "content")
        .put("assetid", video.id)
        .put("program", "Program Name")
        .put("title", "Episode Title")
        .put("length", "3600")              // Duration in seconds
        .put("category", "Entertainment")
        .put("adModel", "2")                // 1=linear, 2=dynamic
    
    // Build channel information
    val channelInfo = JSONObject()
        .put("channelName", video.channelName)
        .put("mediaURL", video.videoUri)
    
    // Start measurement
    mAppSdk?.play(channelInfo)
    mAppSdk?.loadMetadata(metaData)
}
```

---

## Step 5: DCR Video - Playhead Tracking

Report playhead position every 1-2 seconds during active playback.

```kotlin
fun monitorPlayhead(exoPlayer: ExoPlayer) {
    monitorJob = lifecycle.coroutineScope.launch(Dispatchers.Main) {
        while (isActive) {
            if (exoPlayer.isPlaying) {
                val positionMs = exoPlayer.currentPosition
                val durationMs = exoPlayer.duration
                
                val playheadSeconds: Long = if (durationMs <= 0) {
                    // Live content: Report UTC time in seconds
                    System.currentTimeMillis() / 1000
                } else {
                    // VOD content: Report seconds from start
                    positionMs / 1000
                }
                
                // Report playhead position to Nielsen
                mAppSdk?.setPlayheadPosition(playheadSeconds)
            }
            delay(1000)  // Report every 1 second
        }
    }
}
```

> **Important:**
> - VOD Content: Report seconds from the beginning of content
> - Live Content: Report UTC time in seconds

---

## Step 6: DCR Video - Stop/End

Signal playback state changes for accurate measurement.

### Pause Playback

```kotlin
fun appStopMeteringVideo() {
    mAppSdk?.stop()
}
```

### End Content Session (Video End)

```kotlin
fun appEndMeteringVideo() {
    mAppSdk?.end()
}
```

### Close SDK (App Exit Only)

```kotlin
fun appCloseMeteringVideo() {
    mAppSdk?.close()
    mAppSdk = null
}
```

> **Warning:** Only call `close()` when the app exits completely. Use `end()` for normal navigation between screens.

---

## Step 7: DCR Static - Web Content

Measure non-video content like WebViews and articles.

### Start Static Measurement

```kotlin
fun staticLoadMetadata() {
    val metadata = JSONObject()
        .put("type", "static")
        .put("section", "Web View Screen")
        .put("segA", "CustomSegmentA")
        .put("segB", "CustomSegmentB")
        .put("segC", "CustomSegmentC")
        .put("crossId1", "crossReference1")
        .put("crossId2", "crossReference2")
    
    mAppSdk?.loadMetadata(metadata)
}
```

### End Static Measurement

```kotlin
fun staticEnd() {
    mAppSdk?.staticEnd()
}
```

> **Note:** Call `staticLoadMetadata()` when entering a static screen and `staticEnd()` when leaving.

---

## Step 8: DTVR - ID3 Tag Processing

Process Nielsen ID3 watermarks from live TV streams for DTVR measurement.

### Listen for ID3 Metadata (ExoPlayer)

```kotlin
inner class PlayerEventListener : Player.Listener {
    override fun onMetadata(metadata: Metadata) {
        for (i in 0 until metadata.length()) {
            val entry = metadata.get(i)
            if (entry is PrivFrame) {
                val owner = entry.owner
                val data = String(entry.privateData, Charsets.UTF_8)
                
                // Check if it's a Nielsen ID3 tag
                if (owner.contains("nielsen", ignoreCase = true) ||
                    owner.contains("www.nielsen.com", ignoreCase = true)) {
                    appProcessID3tag(owner + data)
                }
            }
        }
    }
}
```

### Send ID3 Tag to Nielsen SDK

```kotlin
fun appProcessID3tag(id3String: String?) {
    try {
        mAppSdk?.sendID3(id3String)
    } catch (e: Exception) {
        Log.e("Nielsen", "Cannot process ID3 tag: ${e.message}")
    }
}
```

> **Note:** Valid Nielsen ID3 tags must contain "www.nielsen.com" and be at least 249 characters.

---

## Step 9: User Opt-Out

Provide users the ability to opt out of Nielsen measurement via WebView.

### Get Opt-Out URL

```kotlin
fun getOptOutUrl(): String {
    return mAppSdk?.userOptOutURLString() ?: ""
}

// Load this URL in a WebView
webView.loadUrl(getOptOutUrl())
```

### Handle Opt-Out Response

```kotlin
webView.webViewClient = object : WebViewClient() {
    override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean {
        val url = request.url.toString()
        
        // Check for Nielsen opt-out response
        if (url.startsWith("nielsenappsdk://")) {
            mAppSdk?.userOptOut(url)
            // Navigate back or show confirmation
            return true
        }
        return false
    }
}
```

### Check Opt-Out Status

```kotlin
fun getOptOutStatus(): Boolean {
    return mAppSdk?.optOutStatus ?: false
}
```

---

## Step 10: Lifecycle Management

Properly manage SDK lifecycle with Android components.

### Register Lifecycle Observer

```kotlin
class NielsenSdkManager : DefaultLifecycleObserver {
    private var monitorJob: Job? = null
    
    fun registerLifecycle(lifecycle: Lifecycle) {
        lifecycle.addObserver(this)
    }
    
    override fun onDestroy(owner: LifecycleOwner) {
        monitorJob?.cancel()
        appEndMeteringVideo()
    }
}
```

### SDK Helper Methods

```kotlin
// Get Nielsen User ID
fun getNielsenUserId(): String = mAppSdk?.nielsenId ?: ""

// Get Device ID
fun getDeviceId(): String = mAppSdk?.deviceId ?: ""

// Get Demographic ID
fun getDemographicId(): String = mAppSdk?.demographicId ?: ""

// Get SDK Meter Version
fun getMeterVersion(): String = AppSdk.getMeterVersion()
```

---

## Quick Reference - AppSdk Methods

| Method | Purpose | When to Call |
|--------|---------|--------------|
| `play(channelInfo)` | Start content session | Video playback begins |
| `loadMetadata(metadata)` | Send content/static info | After play() or for static content |
| `setPlayheadPosition(pos)` | Report playhead position | Every 1-2 seconds during playback |
| `sendID3(tag)` | Process DTVR watermark | When ID3 tag received from stream |
| `stop()` | Pause measurement | Video paused |
| `end()` | End content session | Video playback ends |
| `staticEnd()` | End static session | Leave static content screen |
| `close()` | Destroy SDK instance | App exit only |
| `userOptOutURLString()` | Get opt-out URL | Display opt-out WebView |
| `userOptOut(url)` | Set opt-out preference | When user completes opt-out |

---

## Call Sequence Summary

| Event | DCR Video | DCR Static | DTVR |
|-------|-----------|------------|------|
| Content Start | play() → loadMetadata() | loadMetadata() | play() → loadMetadata() |
| During Playback | setPlayheadPosition() | — | sendID3() |
| Pause | stop() | — | stop() |
| Resume | play() | — | play() |
| Content End | end() | staticEnd() | end() |
| App Exit | close() | close() | close() |
