> ## Documentation Index
> Fetch the complete documentation index at: https://docs.kontext.so/llms.txt
> Use this file to discover all available pages before exploring further.

# API 参考

> Kotlin 视频 SDK 的每个类型、方法、回调和错误。

每个调用都在主线程上进行，每个监听器和回调都在主线程上送达。时间单位为毫秒；价格为 CPM。包名为 `so.kontext.video`。

## `KontextVideo`

入口，唯一对象：`KontextVideo`。

**initialize(context, configuration, listener)** `method`

启动 SDK：保存配置并在后台联系服务器。在 `Application.onCreate` 中调用一次；之后的调用被忽略。`listener` 可选，服务器回答或失败后在主线程上收到结果 `State`。

```kotlin theme={null}
KontextVideo.initialize(this, Configuration("…")) { state -> … }
```

**awaitInitialization()** `suspend method`

等待服务器回答或失败并返回 `State`；已经完成时立即返回：`Enabled`、`Disabled(reason)`（服务器在本次启动中关闭了 SDK）或 `Failed`（无法连接服务器；下一次广告请求会先重试）。

```kotlin theme={null}
val state = KontextVideo.awaitInitialization()
```

**state** `State`

当前状态；`initialize` 之前还有 `NotInitialized`，询问服务器期间为 `Initializing`。

**user** `User?`

你了解的观众信息。一旦知道就设置；下一次广告请求会携带它。

**regulatory** `Regulatory?`

隐私和同意信号。SDK 读取你的同意平台写入默认 `SharedPreferences` 的 IAB 值；在这里设置它没写的部分（COPPA 始终来自你）。

**variantId** `String?`

选择渲染哪种广告，用于与 Kontext 约定的 A/B 测试。

**VERSION** `String`

SDK 版本，与每次请求发送的一致。用于你的日志和崩溃报告。

## `Configuration`

Kotlin 中为 `Configuration("…") { logLevel = … }`，Java 中为 `new Configuration.Builder("…").logLevel(…).build()`。

**publisherToken** `String` *required*

标识发布方。不是密钥。你会得到一个用于测试的 `{publisher}-dev` token 和一个生产 token。

**logLevel** `LogLevel`

SDK 向 Logcat（标签 `KontextVideo`）输出多少：`NONE`、`ERROR`、`WARNING`（默认）、`INFO`、`DEBUG`。

**adServerUrl** `String`

广告服务器，默认是生产服务器。只在针对 Kontext 提供给你的服务器测试时修改。

## `Content`

广告对应的视频。除 `id` 和 `language` 之外的一切都是情境信号。Kotlin 中为 `Content(id, language) { title = … }`，Java 中为 `new Content.Builder(id, language).title(…).build()`。

**id** `String` *required*

你自己为视频设定的 id，任意格式。

**language** `String` *required*

BCP-47，例如 `"en"` 或 `"pt-BR"`。

**mediaRating** `Rating?`

`SAFE` 或 `EXPLICIT`。不知道时留空；露骨内容不会得到要求安全内容的广告。

**url** `String?`

剧集的公开页面。

**title** `String?`

**categories** `List\<String\>?`

IAB 内容分类 3.0 的 id。

**description** `String?`

**durationMillis** `Long?`

毫秒。

**episodeNumber** `Int?`

**keywords** `List\<String\>?`

**genre** `String?`

例如 `"romance"`。

**series** `Content.Series?`

`Content.Series(id) { title = …; episodeCount = … }`。

**属性**

**id** `String` *required*

**title** `String?`

**description** `String?`

**episodeCount** `Int?`

总集数。

## `User`

Kotlin 中为 `User { id = … }`，Java 中为 `new User.Builder().id(…).build()`。

**id** `String?`

你自己为观众设定的唯一标识。

**email** `String?`

明文邮箱，在同意允许时。按原样发送；服务器到达时即哈希，从不存储明文。

**emailHash** `String?`

规范化地址（去空格、小写、UTF-8）的 SHA-256，64 位小写十六进制。按原样发送。

**keywords** `List\<String\>?`

你用自己的话描述的观众兴趣。

## `Regulatory`

Kotlin 中为 `Regulatory { coppa = true }`，Java 中为 `new Regulatory.Builder().coppa(true).build()`。

**gdpr** `Boolean?`

请求是否受 GDPR 约束。

**gdprConsent** `String?`

IAB TCF v2 同意字符串。

**coppa** `Boolean?`

请求是否受 COPPA 约束。无法从设备读取。

**gpp** `String?`

IAB Global Privacy Platform 字符串。

**gppSid** `List\<Int\>?`

适用的 GPP 章节 id。

**usPrivacy** `String?`

旧版 IAB US Privacy（CCPA）字符串。

## `AdFormat`

`OVERLAY`、`PAUSE`、`LINEAR`。

## `VideoAds`

一个视频的广告。每集创建一个；创建不发送任何请求。任意数量可同时存在。观众离开视频时调用 `destroy()`：丢掉引用并不会结束它的广告。

**VideoAds(content, labels)** `constructor`

`labels` 可选：描述这些广告在哪里播放的键值对，例如观众在剧集中所处的阶段或播放器的布局。这个 `VideoAds` 的每个请求都会带上它们，由我们这边的配置决定它们改变什么，例如提供哪些格式。它们在对象的整个生命周期内固定不变，描述的是页面，而不是观众。键只能使用 `a-z`、`0-9` 和 `_`；值不能为空；两者都不超过 64 字节，最多 20 对。其他的会被丢弃，并在 Logcat 给出警告。

```kotlin theme={null}
val ads = VideoAds(Content("episode-1", "en"))
val firstEpisode = VideoAds(content, mapOf("stage" to "first_episode", "layout" to "fullscreen"))
```

**format(format)** `suspend method`

一种格式的广告：在它未展示且有效期间是上次的同一个 `VideoAd`，否则是新的。请求进行中发出的调用共享该请求。请求失败时，持有的广告仍有效则返回它。

抛出 `NoFill`、`Request`、`Destroyed`、`Disabled`、`NotInitialized`、`InitializationFailed`。被取消的协程停止等待；请求本身继续。

```kotlin theme={null}
val ad = ads.format(AdFormat.PAUSE)
```

**format(format, callback)** `method`

供 Java 或不使用协程的代码调用的同一方法：`callback` 是一个 `VideoAdsCallback<VideoAd>`，在主线程上收到 `onSuccess(ad)` 或 `onError(exception)`。

**track(player)** `method`

播放这一集的播放器：Media3 `Player`（`import so.kontext.video.media3.track`，来自 `so.kontext:video-media3`），或任何 [`VideoAdsPlayer`](#videoadsplayer)。SDK 读取它的时间用于请求和事件，并为叠加广告跟随它的播放：叠加广告只在内容播放时展示，内容暂停、结束或播放器切换到其他项目时以 `onAdEnded(INTERRUPTED)` 结束。持有到 `destroy()` 或下一次 `track`；Media3 适配器弱持有你的播放器。在这一集开始在该播放器中播放时调用。没有播放器时，事件带 0（适合预取），叠加广告按自己的时间运行。

```kotlin theme={null}
ads.track(player)
```

**destroy()** `method`

释放视频的广告。所有持有的广告被丢弃并告知广告主未使用；屏幕上的广告以 `onAdEnded(DESTROYED)` 结束；进行中的 `format()` 或 `load()` 抛出 `Destroyed`。幂等。

**content** `Content`

创建时传入的内容。

**isDestroyed** `Boolean`

`destroy()` 之后为 `true`。

**lastRequestId** `String?`

最近一次广告请求的 id，第一次之前为 `null`。广告或无填充看起来不对时，把它告诉 Kontext。

## `VideoAdsPlayer`

SDK 从非 Media3 `Player` 播放器读取的内容。在一个包装你播放器的小适配器中实现它，然后传给 `track(player)`。SDK 在主线程上读取它。

**currentPositionMillis** `Long`

内容的位置，以本集的毫秒数计。

**isPlaying** `Boolean`

观众或你的应用暂停了内容，或内容已结束时为 `false`。缓冲和拖动进度都算作播放。

**playbackSpeed** `Float?`

可选。播放速度，1 为正常，在你的播放器知道时提供。`null`（默认）允许最高约 4 倍。

**addListener(listener) / removeListener(listener)** `method`

可选。在播放器发生变化时立即告诉 `VideoAdsPlayer.Listener`，让叠加广告立刻响应：`onIsPlayingChanged(isPlaying)`、`onSeek()`、`onContentChanged()`（切换到其他项目，或本项目播放到结尾）。没有监听器时，叠加广告在屏幕上期间 SDK 每秒读取 `isPlaying` 四次，并根据位置估计拖动进度。

叠加广告的时长按视频的毫秒数计算。没有 `onSeek()` 和 `playbackSpeed` 时，SDK 把快于约 4 倍真实时间的跳变视为拖动进度，不计入。Media3 适配器会报告所有这些。

```kotlin theme={null}
class MyPlayerAdapter(private val player: MyPlayer) : VideoAdsPlayer {
    override val currentPositionMillis: Long get() = player.positionMillis
    override val isPlaying: Boolean get() = player.state == State.PLAYING || player.state == State.BUFFERING
}
```

## `VideoAd`

一个广告，从服务器的回答到呈现结束。看它的事实，然后 `load()` 或 `reject()`，然后 `show()`，然后恰好一次 `onAdEnded`。最多展示一次。

### 事实

**id** `String`

**format** `AdFormat`

**price** `VideoAd.Price`

**属性**

**value** `Double`

CPM。

**currency** `String`

ISO 4217。

**precision** `Precision`

`EXACT`（广告主的成交价）或 `ESTIMATED`（自有需求，预估）。

**durationMillis** `Long?`

叠加广告：SDK 让它停留多久，`null` 表示直到被结束。线性广告：视频长度。暂停广告始终为 `null`。

### 状态

**isLoaded** `Boolean`

`load()` 已完成。

**isReady** `Boolean`

已加载、未展示、未拒绝、未过期：可以尝试 `show()`。是提示而非保证；`show()` 仍会测量容器。

**invalidationReason** `InvalidationReason?`

广告在展示前结束的原因（如果发生）：`Expired`、`Rejected`、`Destroyed`、`CreativeFailed(code)`。

**wasShown** `Boolean`

成功 `show()` 之后为 `true`。

**hasEnded** `Boolean`

广告开始结束后为 `true`。叠加和暂停广告随即收到 `onAdEnded`，线性广告在其页面关闭后收到。

**isCompleted** `Boolean`

素材是否播放到底：视频到达完成、叠加广告时长到期。与它如何离开屏幕是两回事。

**listener** `VideoAdListener?`

接收广告的各个时刻。持有到 `onAdEnded` 运行完毕，然后释放。

### 方法

**load()** `suspend method`

在屏幕外渲染素材，就绪后返回，之后 `show()` 是即时的。抛出 `CreativeFailed`、`Invalidated`、`Destroyed`、`Disabled`。第二次调用会等待第一次。`load(callback)` 是供 Java 使用的同一方法：一个 `VideoAdsCallback<VideoAd>`。

**reject(reason)** `method`

你不会使用这个广告；服务器告知广告主。`RejectReason.NotUsed`（默认）或 `RejectReason.Outbid(winningPrice, winner)`。`show()` 之前都可用，无论是否已加载。之后抛出 `AlreadyShown`，广告已结束时抛出 `Invalidated`。

```kotlin theme={null}
ad.reject(VideoAd.RejectReason.Outbid(winningPrice = 2.4, winner = "Other network"))
```

**show(container, insets)** `method`

在 `container`（内容播放所在的 `ViewGroup`）中展示叠加或暂停广告。广告在屏幕上即返回。容器改变大小或旋转时广告跟随它；容器变得太小时广告以 `onAdEnded(INTERRUPTED)` 结束。抛错时什么都没展示：`NotLoaded`、`AlreadyShown`、`AnotherAdShowing`、`Invalidated`、`Destroyed`、`Disabled`、`PresenterUnavailable`（容器未附加到窗口、未显示或没有面积）、`ContainerTooSmall` 和 `ContentPaused`（在暂停的内容上展示叠加广告；这三种情况广告都保持就绪）、`WrongPresentation`（线性广告）。把播放器交给 `track` 后，内容停止时叠加广告也会以 `onAdEnded(INTERRUPTED)` 结束。

**参数**

**insets** `androidx.core.graphics.Insets`

在 `container` 内保持空出的空间，单位为像素，叠加在与容器重叠的系统栏和屏幕刘海之上：`Insets.of(0, 0, 0, 200)` 让底部条幅位于它们上方 200 px。默认为 `Insets.NONE`。广告展示期间保持不变，经过改变大小和旋转也是如此。全屏暂停广告忽略它们。见[让广告避开你的控件](https://docs.kontext.so/zh/video/sdk/kotlin/guides.md#让广告避开你的控件)。

**show(activity)** `method`

全屏展示线性广告，在从 `activity` 启动的 SDK 自有 Activity 中。返回键在那里不起作用：由素材自己的控件结束广告。抛错时什么都没展示：上述错误、`PresenterUnavailable`（Activity 正在结束或已销毁；广告保持就绪）、`WrongPresentation`（叠加或暂停广告）。

```kotlin theme={null}
linear.show(this)   // 从你的 Activity
```

**dismiss()** `method`

由你结束叠加或暂停广告；随后同步收到 `onAdEnded(DISMISSED)`。`show()` 之前和结束之后不做任何事。对线性广告抛出 `WrongPresentation`，它只由 SDK 关闭。

`reject`、`show` 和 `dismiss` 标注了 `@Throws(VideoAdException::class)`，因此 Java 代码需要捕获该异常。

## `VideoAdListener`

在主线程上送达。每个方法都有空的默认实现，在 Java 中也是；实现你需要的。`show()` 之前什么都不会送达。之后：曝光和点击，顺序不固定，然后恰好一次 `onAdEnded`，总是最后一个。每个方法的第一个参数都是广告本身，因此一个监听器可以服务多个广告。

**onAdImpression(ad)** `method`

曝光已计数：图片或 HTML 素材在 `show()` 时，视频在第一帧时。最多一次；如果广告在此之前结束，则不会发生。

**onAdClicked(ad, click)** `method`

观众点击了广告。SDK 记录了点击并开始打开其目标。在开始打开时送达，因此是否打开了、打开的是哪一个，都不会报告。每次点击一次。

**click**

**area** `VideoAd.Click.Area`

观众点击的位置：`Creative`（图片、视频、卡片），`EndCard`（视频完成后线性广告的结束卡片），或 `Other(label)`，素材自己的标签，例如 `"cta"`。

**onAdFailed(ad, error)** `method`

`show()` 返回后广告出错。随后跟随 `onAdEnded(FAILED)`。

**error**

**CreativeFailed** `VideoAdException`

素材在屏幕上失败或卡住。`code` 是 VAST 错误码：素材自己的，或 405。

**PresenterUnavailable** `VideoAdException`

线性广告的页面从未启动。

**onAdEnded(ad, reason)** `method`

呈现结束。每次成功的 `show()` 恰好一次，总是最后一个。在这里根据格式和 `reason` 恢复你的内容。叠加广告或暂停广告会同步送达，即使是在你自己的 `dismiss()` 或 `destroy()` 调用内部。线性广告在其页面关闭后送达，因此你可以在这里启动下一个页面。

**reason**

**reason** `VideoAd.EndReason`

谁或什么让广告离开了屏幕；见 [`VideoAd.EndReason`](#videoad-endreason)。素材是否播放到结尾由 `ad.isCompleted` 表示：观众关闭了结束卡片的线性广告是 `CLOSED`，且 `isCompleted == true`。

## `VideoAd.EndReason`

| 原因 | 何时 |
| - | - |
| `COMPLETED` | 广告自行结束：叠加广告时长到期，线性视频播完且没有结尾卡片。 |
| `SKIPPED` | 观众在视频结束前点了线性广告的跳过。 |
| `CLOSED` | 观众点了广告的关闭控件（叠加或暂停卡片的 ✕，线性结尾卡片的 ✕）。暂停卡片的 ✕ 会让内容保持暂停。 |
| `RESUMED` | 观众在全屏暂停广告上点了继续播放。 |
| `FAILED` | 素材失败或卡住，或线性广告的页面从未启动；`onAdFailed` 先到。 |
| `INTERRUPTED` | 呈现因 SDK 之外的原因消失：容器离开窗口或变得放不下广告，或线性广告的页面在它运行时被销毁。对于把播放器交给 `track` 的叠加广告，还包括内容暂停、结束或切换到其他项目。 |
| `DISMISSED` | 你调用了 `dismiss()`。 |
| `DESTROYED` | 你调用了 `destroy()`。 |

## `VideoAdException`

由调用抛出：抛出异常意味着屏幕上什么都没发生，也没有任何事件触发，失败的 `load()` 的 VAST 错误事件除外。其中两个也会在成功的 `show()` 之后通过 `onAdFailed` 送达：`CreativeFailed` 和 `PresenterUnavailable`；此时广告已结束。每一个都是 `VideoAdException` 的子类。

| 异常 | 何时 |
| - | - |
| `NotInitialized` | 从未调用 `KontextVideo.initialize`。 |
| `InitializationFailed` | 无法连接服务器，重试也失败了。每次 `format()` 都会重试。 |
| `Destroyed` | `VideoAds` 已销毁。为下一个内容创建新的。 |
| `Disabled(reason)` | 服务器在本次启动中关闭了 SDK。 |
| `NoFill(reason)` | 服务器没有该格式的广告。见 [`NoFillReason`](#nofillreason)。 |
| `Request` | 请求失败且没有持有该格式的有效广告；`cause` 说明原因。 |
| `CreativeFailed(code)` | 素材在十秒活跃时间内未加载完成；`code` 是 VAST 错误码。 |
| `AlreadyShown` | 已展示过一次；不能再展示或拒绝。 |
| `AnotherAdShowing` | 任意 `VideoAds` 的另一个广告在屏幕上。此广告保持就绪。 |
| `Invalidated(reason)` | 广告在展示前结束：`Expired`、`Rejected`、`Destroyed`、`CreativeFailed(code)`。 |
| `NotLoaded` | `load()` 完成前就 `show()`。广告保持原状。 |
| `ContentPaused` | 在其 `VideoAds` 的内容暂停时对叠加广告调用 `show(container)`。广告保持就绪：内容播放后再展示。 |
| `ContainerTooSmall` | 广告必须缩小到 50% 以下才能放入。`minimumWidthPx` 和 `minimumHeightPx` 是能容纳它的最小容器，包含你的 insets。广告保持就绪。 |
| `PresenterUnavailable` | 现在屏幕上没有东西能承载广告：线性广告的 Activity 正在结束或已销毁，或为叠加/暂停广告给出的容器未附加到窗口、未显示或没有面积。广告保持就绪。 |
| `WrongPresentation` | 对叠加或暂停广告调用 `show(activity)`，对线性广告调用 `show(container)` 或 `dismiss()`。 |

## `NoFillReason`

| 原因 | 含义 |
| - | - |
| `NoAdMatch` | 没有广告活动匹配内容、观众或时机 |
| `FrequencyCap` | 观众达到了为你的应用设置的频次上限。仅在配置了上限时出现 |
| `ContentUnsafe` | 内容不满足品牌安全 |
| `GeoRestricted` | 不在该国家投放 |
| `FormatNotEnabled` | 你的应用未启用该格式，或该格式未对这个 `VideoAds` 发送的 `labels` 启用 |
| `TemporaryError` | 我们这边的问题；稍后再请求 |
| `AlreadyUsed` | SDK 自身的原因：回答是你刚展示或拒绝的广告 |
| `Unknown` | 服务器没有给出原因 |
| `Other(raw)` | 此 SDK 版本不认识的原因，原样透传 |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.