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

# 展示第一个广告

> 创建视频的广告对象，获取广告，提前加载，并在合适的时刻展示：叠加、暂停或线性。

这是 10 分钟集成。开始前请完成[安装](https://docs.kontext.so/zh/video/sdk/kotlin/installation.md)，确保 `KontextVideo.initialize` 在启动时运行。

每种格式都遵循同样的四个步骤：

1. `VideoAds(content)`，对应观众正在看的视频。
2. `ads.format()` 向服务器请求并返回一个 `VideoAd`。
3. `ad.load()` 在屏幕外渲染素材，之后展示即时完成。或者 `ad.reject()`，如果你不会使用它。
4. 在你选的时刻 `ad.show()`。结束时一个 `onAdEnded` 回调。

`format()` 和 `load()` 是 `suspend` 函数；下面的代码片段从 `lifecycleScope` 调用它们。在 Java 中使用它们的回调版本；见[从 Java 调用](https://docs.kontext.so/zh/video/sdk/kotlin/guides.md#从-java-调用)。

## 1. 创建视频的广告对象

每个视频（剧集）一个 `VideoAds`。创建它不发送任何请求。在你使用它的广告期间一直保留它，例如作为播放器 Activity 或 Fragment 的属性，并在页面离开时调用 `destroy()`：丢掉引用并不会结束它的广告。

```kotlin theme={null}
import so.kontext.video.Content
import so.kontext.video.VideoAds

val content = Content("episode-1234", "en") {
    title = "Episode 1"
    durationMillis = 93_000
    genre = "romance"
    series = Content.Series("series-42") { title = "My Marionette Lover"; episodeCount = 6 }
}
val ads = VideoAds(content)
```

只有 `id` 和 `language` 是必填的；其余字段都能改善定向，有什么就发什么。

把播放器交给 SDK，这样事件会带上内容播放位置，叠加广告也会跟随播放：

```kotlin theme={null}
import so.kontext.video.media3.track

ads.track(player)   // 你的 ExoPlayer，这一集在其中播放后调用
```

`track(Player)` 来自 `so.kontext:video-media3`。如果播放器不是 Media3 `Player`，实现 [`VideoAdsPlayer`](https://docs.kontext.so/zh/video/sdk/kotlin/api#videoadsplayer)，以同样方式传入。

## 2. 获取广告

```kotlin theme={null}
lifecycleScope.launch {
    overlayAd = ads.format(AdFormat.OVERLAY)   // PAUSE 存入 pauseAd，LINEAR 存入 linearAd
}
```

`format()` 向服务器请求你传入的格式的广告。在剧集打开时就请求，而不是等到你想展示广告的那一刻：请求和加载需要一点时间，而广告应该立即出现。

服务器没有广告时它抛出 `VideoAdException.NoFill`。这是正常的，不是故障；稍后再请求。

## 3. 加载它，或拒绝它

```kotlin theme={null}
overlayAd?.load()
overlayAd?.listener = this
```

`load()` 在屏幕外渲染素材，就绪后返回，所以 `show()` 是即时的。如果你决定不用这个广告，调用 `overlayAd?.reject()`，广告主会收到通知。

## 4. 展示它

每种格式有自己的时刻和自己的 `show()`。

> [tab] 叠加
>
> 剧集播放时，在你选的时刻出现在播放器上的一条条幅。几秒后，或观众一暂停，它就会自行消失。请在内容播放时展示：在暂停的内容上 `show(container)` 会抛出 `ContentPaused`。
>
> ```kotlin theme={null}
> fun showOverlay() {
>     try {
>         overlayAd?.show(playerContainer)   // 你的视频播放所在的 ViewGroup
>     } catch (e: VideoAdException) {
>         // 没有展示；之后不会有任何回调
>     }
> }
> ```
>
> 想提前移除，对它调用 `dismiss()`。如果你自己的控件覆盖在播放器上，用 `insets` 让广告避开它们；见[让广告避开你的控件](https://docs.kontext.so/zh/video/sdk/kotlin/guides.md#让广告避开你的控件)。

> [tab] 暂停
>
> 观众暂停时出现的卡片或全屏广告。观众继续播放时移除它。
>
> ```kotlin theme={null}
> fun viewerPaused() {
>     player.pause()
>     try { pauseAd?.show(playerContainer) } catch (e: VideoAdException) { }   // 你的视频播放所在的 ViewGroup
> }
>
> fun viewerResumed() {
>     pauseAd?.dismiss()
>     player.play()
> }
> ```
>
> 观众也可以点击全屏暂停广告上的恢复按钮；你在 `onAdEnded` 中恢复播放。

> [tab] 线性
>
> 剧集之间、之前或之后的全屏视频。先暂停你的内容；SDK 在它自己的 Activity 中展示广告。
>
> ```kotlin theme={null}
> fun showLinear() {
>     val ad = linearAd ?: return
>     player.pause()
>     try {
>         ad.show(this)                    // this：你的 Activity
>     } catch (e: VideoAdException) {
>         player.play()                    // 它从未出现，之后不会有任何回调
>     }
> }
> ```
>
> 你在 `onAdEnded` 中恢复内容。系统返回键在广告页面上不起作用：由素材自己的控件结束它。

`show()` 要么抛错且什么都没发生，要么广告已在屏幕上。从那以后由 SDK 运行它直到结束。

## 5. 响应结束

实现 `VideoAdListener`。每个方法都有空的默认实现；你需要的是 `onAdEnded`。每个已展示的广告它恰好到达一次，无论是谁结束了它，所以把恢复逻辑放在这里，别处不放，并从这里请求下一个广告，让它提前就绪。

> [tab] 叠加
>
> 你的内容一直在播放，所以无需恢复。
>
> ```kotlin theme={null}
> override fun onAdEnded(ad: VideoAd, reason: VideoAd.EndReason) {
>     overlayAd = null
>     // 提前准备下一个广告：format()，然后 load()
> }
> ```

> [tab] 暂停
>
> 观众点击全屏暂停广告上的恢复按钮时，在这里恢复播放。观众通过你自己的控件恢复时，`viewerResumed()` 已经处理了。
>
> ```kotlin theme={null}
> override fun onAdEnded(ad: VideoAd, reason: VideoAd.EndReason) {
>     pauseAd = null
>     if (reason == VideoAd.EndReason.RESUMED) player.play()   // 观众点击了广告上的恢复按钮
>     // 提前准备下一个广告：format()，然后 load()
> }
> ```

> [tab] 线性
>
> 你的内容为广告暂停了；无论原因是什么都恢复它，除非观众已经离开。检查 `ads.isDestroyed`，而不是原因：只要观众离开了它就是 `true`，无论广告以什么原因结束。
>
> ```kotlin theme={null}
> override fun onAdEnded(ad: VideoAd, reason: VideoAd.EndReason) {
>     linearAd = null
>     if (ads.isDestroyed) return          // 观众已离开：无需恢复
>     player.play()
>     // 提前准备下一个广告：format()，然后 load()
> }
> ```

## 6. 清理

观众离开视频时调用 `destroy()`：在 Activity 的 `onDestroy()`、Fragment 的 `onDestroyView()`，或 Compose 中 `DisposableEffect` 的 `onDispose` 里。它丢弃所有持有的广告，告诉服务器哪些没有被使用，并结束屏幕上的那个。

```kotlin theme={null}
override fun onDestroy() {
    super.onDestroy()
    ads.destroy()
}
```

## 完整代码

每集一个 Activity，在 `ExoPlayer` 中播放。选择与你的格式对应的标签页：

> [tab] 叠加
>
> ```kotlin theme={null}
> class PlayerActivity : ComponentActivity(), VideoAdListener {
>     private lateinit var player: ExoPlayer
>     private lateinit var playerContainer: ViewGroup
>     private lateinit var ads: VideoAds
>     private var overlayAd: VideoAd? = null
>
>     override fun onCreate(savedInstanceState: Bundle?) {
>         super.onCreate(savedInstanceState)
>         setContentView(R.layout.activity_player)          // 一个包含 PlayerView 的 FrameLayout
>         playerContainer = findViewById(R.id.player_container)
>         player = ExoPlayer.Builder(this).build()
>         findViewById<PlayerView>(R.id.player_view).player = player
>         ads = VideoAds(Content("episode-1234", "en"))
>         ads.track(player)
>         lifecycleScope.launch { prepare() }
>     }
>
>     private suspend fun prepare() {
>         try {
>             val ad = ads.format(AdFormat.OVERLAY)
>             ad.load()
>             ad.listener = this
>             overlayAd = ad
>         } catch (e: VideoAdException) {
>             // 这次无填充；在下一个时刻再请求
>         }
>     }
>
>     fun showOverlay() {                                     // 你选的时刻
>         try { overlayAd?.show(playerContainer) } catch (e: VideoAdException) { }
>     }
>
>     override fun onAdEnded(ad: VideoAd, reason: VideoAd.EndReason) {
>         overlayAd = null
>         if (!ads.isDestroyed) lifecycleScope.launch { prepare() }
>     }
>
>     override fun onDestroy() {
>         super.onDestroy()
>         ads.destroy()
>         player.release()
>     }
> }
> ```

> [tab] 暂停
>
> ```kotlin theme={null}
> class PlayerActivity : ComponentActivity(), VideoAdListener {
>     private lateinit var player: ExoPlayer
>     private lateinit var playerContainer: ViewGroup
>     private lateinit var ads: VideoAds
>     private var pauseAd: VideoAd? = null
>
>     override fun onCreate(savedInstanceState: Bundle?) {
>         super.onCreate(savedInstanceState)
>         setContentView(R.layout.activity_player)          // 一个包含 PlayerView 的 FrameLayout
>         playerContainer = findViewById(R.id.player_container)
>         player = ExoPlayer.Builder(this).build()
>         findViewById<PlayerView>(R.id.player_view).player = player
>         ads = VideoAds(Content("episode-1234", "en"))
>         ads.track(player)
>         lifecycleScope.launch { prepare() }
>     }
>
>     private suspend fun prepare() {
>         try {
>             val ad = ads.format(AdFormat.PAUSE)
>             ad.load()
>             ad.listener = this
>             pauseAd = ad
>         } catch (e: VideoAdException) {
>             // 这次无填充；在下一个时刻再请求
>         }
>     }
>
>     fun viewerPaused() {
>         player.pause()
>         try { pauseAd?.show(playerContainer) } catch (e: VideoAdException) { }
>     }
>
>     fun viewerResumed() {
>         pauseAd?.dismiss()
>         player.play()
>     }
>
>     override fun onAdEnded(ad: VideoAd, reason: VideoAd.EndReason) {
>         pauseAd = null
>         if (ads.isDestroyed) return
>         if (reason == VideoAd.EndReason.RESUMED) player.play()   // 观众点击了广告上的恢复按钮
>         lifecycleScope.launch { prepare() }
>     }
>
>     override fun onDestroy() {
>         super.onDestroy()
>         ads.destroy()
>         player.release()
>     }
> }
> ```

> [tab] 线性
>
> ```kotlin theme={null}
> class PlayerActivity : ComponentActivity(), VideoAdListener {
>     private lateinit var player: ExoPlayer
>     private lateinit var ads: VideoAds
>     private var linearAd: VideoAd? = null
>
>     override fun onCreate(savedInstanceState: Bundle?) {
>         super.onCreate(savedInstanceState)
>         setContentView(R.layout.activity_player)          // 一个包含 PlayerView 的 FrameLayout
>         player = ExoPlayer.Builder(this).build()
>         findViewById<PlayerView>(R.id.player_view).player = player
>         ads = VideoAds(Content("episode-1234", "en"))
>         ads.track(player)
>         lifecycleScope.launch { prepare() }
>     }
>
>     private suspend fun prepare() {
>         try {
>             val ad = ads.format(AdFormat.LINEAR)
>             ad.load()
>             ad.listener = this
>             linearAd = ad
>         } catch (e: VideoAdException) {
>             // 这次无填充；在下一个时刻再请求
>         }
>     }
>
>     fun showLinear() {                                      // 你选的时刻
>         val ad = linearAd ?: return
>         player.pause()
>         try { ad.show(this) } catch (e: VideoAdException) { player.play() }
>     }
>
>     override fun onAdEnded(ad: VideoAd, reason: VideoAd.EndReason) {
>         linearAd = null
>         if (ads.isDestroyed) return                         // onDestroy() 之后，无论原因
>         player.play()
>         lifecycleScope.launch { prepare() }
>     }
>
>     override fun onDestroy() {
>         super.onDestroy()
>         ads.destroy()
>         player.release()
>     }
> }
> ```


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