> ## 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/swift/installation.md)，确保 `KontextVideo.shared.initialize(with:)` 在启动时运行。

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

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

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

每个视频（剧集）一个 `VideoAds`。创建它不发送任何请求。

```swift theme={null}
import KontextVideoSDK

let content = KontextVideo.Content(
    id: "episode-1234",
    language: "en",
    title: "Episode 1",
    duration: 93,
    genre: "romance",
    series: .init(id: "series-42", title: "My Marionette Lover", episodeCount: 6)
)
let ads = VideoAds(content: content)
```

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

告诉 SDK 内容在哪里播放，这样事件会带上内容播放位置：

```swift theme={null}
ads.trackPlayhead(of: player)   // 你的 AVPlayer，这一集在其中播放后调用
```

如果播放器不是 `AVPlayer`，改为设置 `ads.playheadProvider = { seconds }`。

## 2. 获取广告

```swift theme={null}
overlayAd = try await ads.format(.overlay)   // .pause 存入 pauseAd，.linear 存入 linearAd
```

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

服务器没有广告时它抛出 `VideoAdError.noFill(reason:)`。这是正常的，不是故障；稍后再请求。

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

```swift theme={null}
try await overlayAd?.load()
overlayAd?.delegate = self
```

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

## 4. 展示它

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

**叠加**

剧集播放时，在你选的时刻出现在播放器上的一条条幅。几秒后它会自行消失。

```swift theme={null}
func showOverlay() {
    try? overlayAd?.show(in: playerView)   // 你的视频播放所在的视图
}
```

想提前移除，对它调用 `dismiss()`。

**暂停**

观众暂停时出现的卡片或全屏广告。观众继续播放时移除它。

```swift theme={null}
func viewerPaused() {
    player.pause()
    try? pauseAd?.show(in: playerView)   // 你的视频播放所在的视图
}

func viewerResumed() {
    try? pauseAd?.dismiss()
    player.play()
}
```

观众也可以点击全屏暂停广告上的恢复按钮；你在 `didEnd` 中恢复播放。

**线性**

剧集之间、之前或之后的全屏视频。先暂停你的内容；广告由 SDK 自行呈现。

```swift theme={null}
func showLinear() {
    guard let linearAd else { return }
    player.pause()
    do {
        try linearAd.show()              // 从最上层的视图控制器呈现；或 show(from: viewController)
    } catch {
        player.play()                    // 它从未出现，之后不会有任何回调
    }
}
```

你在 `didEnd` 中恢复内容。

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

## 5. 响应结束

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

**叠加**

你的内容一直在播放，所以无需恢复。

```swift theme={null}
extension PlayerController: VideoAdDelegate {
    func videoAd(_ ad: VideoAd, didEnd reason: VideoAd.EndReason) {
        overlayAd = nil
        // 提前准备下一个广告：format()，然后 load()
    }
}
```

**暂停**

观众点击全屏暂停广告上的恢复按钮时，在这里恢复播放。观众通过你自己的控件恢复时，`viewerResumed()` 已经处理了。

```swift theme={null}
extension PlayerController: VideoAdDelegate {
    func videoAd(_ ad: VideoAd, didEnd reason: VideoAd.EndReason) {
        pauseAd = nil
        if reason == .resumed { player.play() }        // 观众点击了广告上的恢复按钮
        // 提前准备下一个广告：format()，然后 load()
    }
}
```

**线性**

你的内容为广告暂停了；无论原因是什么，都恢复它。

```swift theme={null}
extension PlayerController: VideoAdDelegate {
    func videoAd(_ ad: VideoAd, didEnd reason: VideoAd.EndReason) {
        linearAd = nil
        player.play()
        // 提前准备下一个广告：format()，然后 load()
    }
}
```

## 6. 清理

观众离开视频时调用 `destroy()`。它丢弃所有持有的广告，告诉服务器哪些没有被使用，并结束屏幕上的那个。

```swift theme={null}
ads.destroy()
```

## 完整代码

每集一个控制器。选择与你的格式对应的标签页：

**叠加**

```swift theme={null}
@MainActor
final class PlayerController: VideoAdDelegate {
    let player: AVPlayer
    let playerView: UIView
    let ads: VideoAds
    var overlayAd: VideoAd?

    init(content: KontextVideo.Content, player: AVPlayer, playerView: UIView) {
        self.player = player
        self.playerView = playerView
        ads = VideoAds(content: content)
        ads.trackPlayhead(of: player)
        Task { await prepare() }
    }

    func prepare() async {
        do {
            let ad = try await ads.format(.overlay)
            try await ad.load()
            ad.delegate = self
            overlayAd = ad
        } catch {
            // 这次无填充；在下一个时刻再请求
        }
    }

    func showOverlay() {                         // 你选的时刻
        try? overlayAd?.show(in: playerView)
    }

    func videoAd(_ ad: VideoAd, didEnd reason: VideoAd.EndReason) {
        overlayAd = nil
        Task { await prepare() }
    }

    func leave() {
        ads.destroy()
    }
}
```

**暂停**

```swift theme={null}
@MainActor
final class PlayerController: VideoAdDelegate {
    let player: AVPlayer
    let playerView: UIView
    let ads: VideoAds
    var pauseAd: VideoAd?

    init(content: KontextVideo.Content, player: AVPlayer, playerView: UIView) {
        self.player = player
        self.playerView = playerView
        ads = VideoAds(content: content)
        ads.trackPlayhead(of: player)
        Task { await prepare() }
    }

    func prepare() async {
        do {
            let ad = try await ads.format(.pause)
            try await ad.load()
            ad.delegate = self
            pauseAd = ad
        } catch {
            // 这次无填充；在下一个时刻再请求
        }
    }

    func viewerPaused() {
        player.pause()
        try? pauseAd?.show(in: playerView)
    }

    func viewerResumed() {
        try? pauseAd?.dismiss()
        player.play()
    }

    func videoAd(_ ad: VideoAd, didEnd reason: VideoAd.EndReason) {
        pauseAd = nil
        if reason == .resumed { player.play() }   // 观众点击了广告上的恢复按钮
        Task { await prepare() }
    }

    func leave() {
        ads.destroy()
    }
}
```

**线性**

```swift theme={null}
@MainActor
final class PlayerController: VideoAdDelegate {
    let player: AVPlayer
    let playerView: UIView
    let ads: VideoAds
    var linearAd: VideoAd?

    init(content: KontextVideo.Content, player: AVPlayer, playerView: UIView) {
        self.player = player
        self.playerView = playerView
        ads = VideoAds(content: content)
        ads.trackPlayhead(of: player)
        Task { await prepare() }
    }

    func prepare() async {
        do {
            let ad = try await ads.format(.linear)
            try await ad.load()
            ad.delegate = self
            linearAd = ad
        } catch {
            // 这次无填充；在下一个时刻再请求
        }
    }

    func showLinear() {                          // 你选的时刻
        guard let linearAd else { return }
        player.pause()
        do { try linearAd.show() } catch { player.play() }
    }

    func videoAd(_ ad: VideoAd, didEnd reason: VideoAd.EndReason) {
        linearAd = nil
        player.play()
        Task { await prepare() }
    }

    func leave() {
        ads.destroy()
    }
}
```


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