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

# Jetpack Compose

> 通过 so.kontext:video-compose 在 Jetpack Compose 中展示视频广告：rememberVideoAds 和 VideoAdHost。

SDK 用 Android View 绘制广告。`so.kontext:video-compose` 提供两个 Compose 函数，页面无需自己编写承载视图、副作用和监听器连接。

## 添加模块

```kotlin theme={null}
dependencies {
    implementation("so.kontext:video:0.1.0")
    implementation("so.kontext:video-compose:0.1.0")
}
```

它支持 Compose 1.6 或更高版本，并使用你应用自己的 Compose 版本。

## 播放器页面

```kotlin theme={null}
@Composable
fun EpisodeScreen(content: KontextVideo.Content, player: ExoPlayer) {
    val ads = rememberVideoAds(content) { it.track(player) }
    var adOnScreen by remember { mutableStateOf<VideoAd?>(null) }
    val scope = rememberCoroutineScope()

    Box(Modifier.fillMaxSize()) {
        AndroidView(factory = { PlayerView(it).apply { this.player = player } }, modifier = Modifier.fillMaxSize())
        VideoAdHost(
            ad = adOnScreen,
            modifier = Modifier.fillMaxSize(),
            onEnded = { _, reason ->
                adOnScreen = null
                if (reason == VideoAd.EndReason.RESUMED) player.play()
            },
        )
    }

    // 观众暂停：展示暂停广告
    val onPause = {
        player.pause()
        scope.launch {
            adOnScreen = try { ads.format(AdFormat.PAUSE).apply { load() } } catch (e: VideoAdException) { null }
        }
    }
}
```

提前加载广告能让 `show` 立即完成：把已加载的广告保存在状态中，需要时再设置为 `adOnScreen`。

## `rememberVideoAds`

```kotlin theme={null}
val ads = rememberVideoAds(content, labels = mapOf("stage" to "free")) { ads ->
    ads.track(player)           // 每创建一个 VideoAds 运行一次
}
```

* 创建该集的 `VideoAds`，`content.id` 变化时创建新的。
* 组合离开或剧集变化时销毁它。销毁会结束屏幕上的广告，线性广告也是如此：在片尾广告的 `onEnded` 之后再切换到下一集。
* 旋转会重建组合，也会重建 `VideoAds`。在 Activity 上为旋转声明 `android:configChanges`，或者把 `VideoAds` 放在 `ViewModel` 中（见下文）。

## `VideoAdHost`

| 参数 | |
| - | - |
| `ad` | 要展示的广告。宿主完成布局后展示已加载的广告；`null` 或另一个广告会关闭当前展示的广告。 |
| `modifier` | 广告的位置：通常是叠在播放器上的 `Box` 中的 `Modifier.fillMaxSize()`。 |
| `insets` | `PaddingValues`，让叠加广告或暂停广告避开你的控件。 |
| `onShowFailed` | 广告无法展示，原因与 `show` 抛出的相同（`AnotherAdShowing`、`ContentPaused`、`ContainerTooSmall` 等）。没有展示任何内容，之后也不会有 `onEnded`。 |
| `onImpression`、`onClicked` | 展示已计数；观众点击了广告。 |
| `onEnded` | 广告的展示结束，附带原因。在这里把你的状态设回 `null`。 |

* **所有格式。** 叠加广告或暂停广告展示在宿主所在的位置。线性广告从承载该组合的 Activity 全屏展示；宿主不会关闭它，它自行结束或由观众结束。
* **宿主持有广告的监听器。** 通过宿主的回调接收广告事件，而不是 `ad.listener`。
* **离开组合** 会关闭屏幕上的叠加广告或暂停广告。

## 在旋转后保留广告

把 `VideoAds` 和广告放在 `ViewModel` 中；宿主展示状态中的内容：

```kotlin theme={null}
class EpisodeViewModel : ViewModel() {
    val ads = VideoAds(content)
    var adOnScreen by mutableStateOf<VideoAd?>(null)
    override fun onCleared() = ads.destroy()
}

@Composable
fun EpisodeScreen(viewModel: EpisodeViewModel, player: ExoPlayer) {
    DisposableEffect(player) {
        viewModel.ads.track(player)       // 重建后的新播放器
        onDispose { }
    }
    Box(Modifier.fillMaxSize()) {
        // 播放器
        VideoAdHost(ad = viewModel.adOnScreen, modifier = Modifier.fillMaxSize(), onEnded = { _, _ -> viewModel.adOnScreen = null })
    }
}
```

## 竖向翻页

为每一页提供自己的 `VideoAds` 和宿主；只在当前页展示广告：

```kotlin theme={null}
VerticalPager(state = pagerState) { page ->
    val episode = episodes[page]
    val ads = rememberVideoAds(episode.content)
    var adOnScreen by remember { mutableStateOf<VideoAd?>(null) }
    Box(Modifier.fillMaxSize()) {
        // 该页的播放器
        VideoAdHost(ad = adOnScreen.takeIf { pagerState.currentPage == page }, modifier = Modifier.fillMaxSize(),
            onEnded = { _, _ -> adOnScreen = null })
    }
}
```

被滑走的页面上的广告会在大约一秒内结束（`INTERRUPTED`），为下一页的广告腾出屏幕。

## 不使用该模块

手动实现同样的效果：用 `AndroidView` 承载一个 remember 的 `FrameLayout`，在其上调用 `show(container)`，在 `DisposableEffect` 中设置广告的监听器并在 `onDispose` 中清除，在 `onDispose` 中销毁 `VideoAds`。对于线性广告，把承载该组合的 Activity 传给 `show(activity)`。


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