Skip to content

叙事植入 —— NovvyInsertAd ​

在播放器区域内铺满展示的植入广告,一集一个实例。无 load()、无 show() —— 嵌入 NovvyInsertAdView 后,宿主只需告知「当前在屏的是哪一集」,SDK 自动完成请求 → 预载 → 到点展示 → 关闭 → 请求下一支,直到本集无货(onChainFinished)。

需要 Android SDK 1.1.2 / iOS SDK 1.1.2

两端行为一致。低于该版本的 SDK 上此广告位不生效。

对接流程 ​

每集创建一个实例  NovvyInsertAd(adUnitId, episodeNumber)
        ↓
在播放器之上嵌入 NovvyInsertAdView(ad, player)     // 只绑定,不请求
        ↓
翻页回调里对新在屏的那一集  setActive(true)         // 上一集自动站下,不要手动配对
        ↓
onAdLoaded → onAdShowed(播放器已暂停)→ onAdClosed(播放器已恢复)
        ↓         ↑                                            │
        └─────────┴────── SDK 自动请求下一支 ←──────────────────┘
        ↓
onChainFinished                                    // 本集结束,终止
        ↓
页面回收(离开可视范围 ±1 集之外)→ destroy()

宿主必守五条 ​

  1. 不要循环播放(setLooping(false))—— 循环播放器永远不会播完,剧尾创意无法触发。
  2. 只对新在屏的那一集调 setActive(true),上一集自动站下;不要手动配对 setActive(false)。
  3. 广告视图必须与播放器同时挂载,不要挡在播放器的就绪状态之后。attach 决定这一集什么时候向 SDK 认领槽位,if (!ready) return spinner; 这类早退会把认领推迟整整一个 initialize() 的时长 —— 这期间上一集仍是 active 链,仍在请求、仍在驱动播放。loading 指示器应该只盖住视频层,不要包住整个 Stack。
  4. 相邻一集的实例建议留着(±1 页),只在更远处才 destroy()。这是实例管理上的建议,不再是 SDK 保活的必要条件 —— 链条现在活到 destroy() 为止,widget 重建不会再销毁它;但实例本身没了就谈不上在正确的时机 destroy()。给页面加 AutomaticKeepAliveClientMixin(见 example/lib/story_demo_page.dart)或开 allowImplicitScrolling 仍是最省心的做法。 NovvyInsertAdView 与 NovvyInsertAd 应当同生共死:不要单独摘掉视图而保留实例。视图没了、实例又还是 active 链时,之后加载的创意会挂进一个离屏容器 —— 不可见、无曝光,而 SDK 仍会在插入点暂停播放器。
  5. 右上角留空给关闭按钮,广告展示期间禁用自有的进度拖动与点击暂停。

类成员 ​

成员说明
NovvyInsertAd(adUnitId, episodeNumber, episodeUrl)episodeNumber 是这个实例代表的集数,必须显式传(翻页组件会预建相邻页,读全局上下文会串集)
setActive(bool)告知这一集是否在屏。true 启动/恢复本集链条。可以在 NovvyInsertAdView 建好之前调(例如播放器还在 initialize 时就翻到了这一页),平台侧会缓冲到广告对象建立后补发
onAdLoaded一支广告已就绪,等待到点(一条链会多次触发)
onAdShowed广告展示(播放器已由 SDK 暂停)
onAdClosed广告关闭(播放器已恢复)
onChainFinished本集广告全部结束,终止;之后再 setActive(true) 不会重启
onAdFailedToLoad请求失败
notifySeek(fromMs, toMs)可选:上报宿主发起的跳转,让 SDK 精确判断插入点是否被跳过
notifyMediaItemChanged()可选:播放器换了内容而未走 setActive(false) 时上报
destroy()释放。先于其绑定的播放器销毁
NovvyInsertAd.viewType'ai.novvy.novvy_ads/insert_ad'(内部使用)

播放器绑定 ​

本插件不依赖任何播放器包,宿主实现 NovvyInsertPlayerBinding 桥接自有播放器:

dart
class MyPlayerBinding implements NovvyInsertPlayerBinding {
  final VideoPlayerController controller;
  MyPlayerBinding(this.controller);

  @override
  int get currentPositionMs => controller.value.position.inMilliseconds;
  @override
  bool get isPlaying => controller.value.isPlaying;
  @override
  int get durationMs => controller.value.duration.inMilliseconds;
  @override
  void pause() => controller.pause();
  @override
  void play() => controller.play();
  @override
  void seekTo(int positionMs) =>
      controller.seekTo(Duration(milliseconds: positionMs));
}
成员类型说明
currentPositionMsint当前播放位置(毫秒)
isPlayingbool是否正在播放
durationMsint总时长;返回 0 则剧尾创意不会触发
pause() / play() / seekTo(ms)void广告展示期间也必须照常执行

pause / play / seekTo 无条件执行:广告展示时宿主自有 UI 仍在广告之上,用户仍能碰到进度条,而进度条的常规手势是「按下暂停、松手播放」。那下松手若不被撤销,剧集音轨会在广告底下响起来 —— 画面被盖住,声音是唯一症状。SDK 会重新下发 pause() 并把位置拽回,但前提是这三个方法照做。

示例 ​

dart
final ad = NovvyInsertAd(adUnitId: 'your_ad_unit_id', episodeNumber: 3);
ad.onAdShowed = () { /* 已展示 */ };
ad.onAdClosed = () { /* 已关闭 */ };
ad.onChainFinished = () { /* 本集广告结束 */ };

// 播放器之上铺满:
Stack(
  fit: StackFit.expand,
  children: [
    VideoPlayer(controller),
    Positioned.fill(child: NovvyInsertAdView(ad: ad, player: myBinding)),
    ...yourOverlayUi,        // 可以压在广告之上,右上角留空
  ],
)

// 翻页时:
onPageChanged: (index) { if (index == myIndex) ad.setActive(true); }

// 页面回收时:
ad.destroy();

完整可运行示例见 example/lib/story_demo_page.dart(含 video_player 绑定与纵向翻页)。

事件回调 ​

一集内多支广告串行,因此 Loaded/Showed/Closed 会重复触发。

回调触发时机
onAdLoaded一支广告已就绪,等待到点(一条链会多次触发)
onAdFailedToLoad(error)请求失败,error 取值见错误类型
onAdShowed广告展示(播放器已由 SDK 暂停)
onAdClosed广告关闭(播放器已恢复)
onChainFinished本集广告全部结束,终止

回调统一在主 isolate(UI 线程) 触发。

本文档仅供技术对接使用,请勿外传。