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

# 指南

> 第一个广告之外的配方：同时加载多个广告、你自己的竞价、无填充、播放位置、下一集、观众。

## 同时加载多个广告

要同时准备多种格式，并发地请求并加载它们。每种格式并行请求和渲染，某种格式没有广告不会影响其他格式：

```swift theme={null}
func prepare(_ format: AdFormat) async -> VideoAd? {
    do {
        let ad = try await ads.format(format)
        try await ad.load()
        ad.delegate = self
        return ad
    } catch {
        return nil                     // 这次该格式无填充
    }
}

async let overlay = prepare(.overlay)
async let pause = prepare(.pause)
async let linear = prepare(.linear)
(overlayAd, pauseAd, linearAd) = await (overlay, pause, linear)
```

## 运行你自己的竞价

`VideoAd` 在渲染前就带着价格，所以你可以和其他广告网络比较后再决定：

```swift theme={null}
let ad = try await ads.format(.pause)
if ad.price.value >= otherNetworkCPM {
    try await ad.load()
    pauseAd = ad
} else {
    try ad.reject(.outbid(winningPrice: otherNetworkCPM, winner: "Other network"))
}
```

`price.value` 是以 `price.currency` 计的 CPM。`price.precision` 说明它是广告主的成交价（`.exact`）还是自有需求的预估（`.estimated`）。

`reject` 立即告知服务器，服务器告诉广告主其胜出未产生曝光。另一个广告网络以价格胜出时用 `.outbid` 并带上胜出价格和广告网络，让我们能针对你的流量优化出价；其他任何原因用 `.notUsed`（默认）：时机过了、观众离开了。拒绝是可选的；你从未使用的广告会在过期或你调用 `destroy()` 时结算。

## 处理无填充

服务器没有该格式的广告时 `format(_:)` 抛出 `VideoAdError.noFill(reason:)`。这不是失败，SDK 从不自行重试；在之后的时刻再问。

```swift theme={null}
do {
    pauseAd = try await ads.format(.pause)
} catch VideoAdError.noFill(let reason) {
    switch reason {
    case .noAdMatch: break             // 此刻没有广告活动适合这一集或这位观众
    case .geoRestricted: break         // 观众所在的国家不投放广告
    default: break                     // frequencyCap、contentUnsafe、temporaryError……
    }
}
```

对你的代码来说它们的意思都一样：这次没有广告，在下一个时刻再请求。如果想了解填充率低的原因，可以记录下原因。

## 把播放位置告诉 SDK

每次请求和事件都带有观众在这一集中的位置。剧集开始播放后，用以下两种方式之一把播放器交给 SDK。

使用 `AVPlayer` 时，把它交给 SDK，SDK 会在需要时读取它的时间：

```swift theme={null}
ads.trackPlayhead(of: player)
```

使用其他播放器时，提供一个返回当前位置（秒）的闭包：

```swift theme={null}
ads.playheadProvider = { [weak self] in self?.player.currentTime ?? 0 }
```

在这一集开始播放时调用其中之一，而不是在创建 `VideoAds` 时：在那之前播放器的时间属于上一集。在那之前 SDK 发送 0，这对预取来说是正确的。

## 下一集

当前一集播放时创建下一集的 `VideoAds`，预取它的广告，观众离开时销毁当前的。任意数量的实例可以同时存在；SDK 让整个应用同一时间最多一个广告在屏幕上。

```swift theme={null}
let next = VideoAds(content: nextEpisode)
Task { nextLinear = try? await next.format(.linear); try? await nextLinear?.load() }

// 观众继续看下一集时：
ads.destroy()
ads = next
ads.trackPlayhead(of: player)     // 播放器现在播放下一集
```

在剧集真正开始在播放器中播放时调用 `trackPlayhead(of:)`，而不是创建时：在那之前播放器的时间属于上一集。

## 观众是谁

把你知道的信息设置到共享实例上；下一次请求会携带它：

```swift theme={null}
KontextVideo.shared.user = .init(id: "user-1234", keywords: ["romance", "drama"])
KontextVideo.shared.regulatory = .init(coppa: false)
KontextVideo.shared.variantId = "B"     // 与 Kontext 约定的 A/B 测试
```


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