Vega中的客户端广告插入
在客户端广告插入 (CSAI) 中,视频播放器独立管理内容和广告播放。与服务器端广告插入 (SSAI) 不同,服务器将广告段拼接到内容清单中,CSAI保持内容流不变。客户端应用程序管理内容和广告流之间的切换。
CSAI支持动态广告插入,因为客户端使用视频广告服务模板 (VAST) 或视频多广告播放列表 (VMAP) 等协议在运行时从广告服务器获取广告。无需修改内容流,即可针对每个用户、每个会话或每个内容实时投放广告。这样无需对服务器端清单进行任何更改即可实现个性化广告体验。
CSAI为应用提供了管理广告时间、播放和过渡的完全控制权。该应用对广告生命周期拥有完全所有权:决定何时触发广告时段,控制广告播放器的初始化和播放行为,以及管理共享界面,以便在主要内容和广告之间进行精确的帧切换。
CSAI技术
Vega支持两种确定何时插入广告的方法:带外(标准)和带内(高级)。
标准CSAI:带外广告
在标准CSAI中,客户端应用依靠外部信息,而不是清单标签来确定何时插入广告。播放器接收未经修改的内容清单并单独处理广告逻辑。
广告触发方法包括:
- VMAP: 播放器获取一个外部XML文件,该文件在特定时间戳(例如,在0秒、300秒)安排广告时段。
- 硬编码逻辑: 应用程序根据播放器当前的时间位置以固定的间隔触发广告请求(例如,每20秒一次)。
- 边车文件: 该应用从内容管理系统 (CMS) 下载单独的配置文件 (JSON/XML),该文件列出了特定内容的广告中断时间。
- 广告SDK: SDK(如Google Interactive Media Ads)通过广告规则在内部管理时间,从而无需开发者管理特定的时间戳或清单标签。
在典型的带外实现中,广告标记是在用户界面层中定义的(例如,硬编码或从节目表中获取),位置侦听器直接触发插入。不会向服务层发送任何广告信息。
高级CSAI:带内广告
在高级CSAI中,播放器监控清单中是否有表明广告机会的直播中元数据标记。
常见的标记包括:
| 标记 | 协议 |
|---|---|
#EXT-X-CUE-OUT / #EXT-X-CUE-IN |
HTTP直播流媒体(HLS) |
#EXT-X-DATERANGE |
HLS间隙 |
| 有线电信工程师协会 (SCTE) -35信号(转换成清单标签) | HLS /HTTP上的动态自适应流媒体 (DASH) |
当播放器遇到标记(例如#EXT-X-CUE-OUT)时,它会将其解释为广告利用机会,并在指定的持续时间内向广告服务器发起VAST或VMAP请求。在主要内容继续播放的同时,会同时提取和缓冲广告内容。仅当达到广告展示时间时,主要内容才会暂停。届时,预计该广告可以开始播放,以提供无缝体验。
在使用无头方法的带内实现中,用户界面可以通过sendMessage({type: 'adList', ads})将广告列表发送到服务层,以提供与清单解析所生成数据相同的数据。该服务将广告列表存储在PlayerSession上,并在getCurrentPlaybackPosition() 期间处理广告标记。当达到广告的预缓冲窗口时(startTime前5秒),该服务会向用户界面发送{type: 'adMarkers', ad}消息,然后触发广告插入。
播放同步策略
两种实现方法都支持两种策略,用于在过渡期间协调主播放器和广告播放器。
预缓冲广告(无缝)
广告播放器在过渡之前会预先初始化并缓冲。这提供了没有视觉干扰的无缝用户体验。
- 主要内容播放。
- 检测到广告标记。使用
autoPlay=false初始化广告播放器。 - 广告播放器缓冲区。
canplay事件触发 (JS) 或服务发送READY状态(无头)。 - 用户界面收到就绪信号。广告已准备好播放,等待预定的播放时间。
- 位置侦听器会检测到已达到广告开始时间。暂停主要内容。
- 主要内容已暂停。清除主展示位,设置广告展示位,播放广告。
- 广告结束。清理广告展示位,清理广告播放器,设置主展示位,恢复主要内容的播放。
特征:
- 没有视觉干扰
- 播放前对广告内容进行预缓冲
- 最适合优质观看体验
autoPlay设置:false(手动控制)
即时自动播放广告(有间隔)
广告中断发生时,将按需初始化广告播放器。由于广告在播放时使用autoPlay=true加载,因此在广告播放器初始化期间可能会有短暂的过渡间隔。
- 主要内容播放。
- 检测到广告标记。暂停主要内容。
- 主要内容已暂停。清除主界面,使用
autoPlay=true初始化广告播放器。 - 广告播放器已准备就绪。设置广告展示位,播放自动开始。
- 广告结束。清理广告展示位,清理广告播放器,设置主展示位,恢复主要内容的播放。
特征:
- 过渡期间可能会出现短暂的黑屏或暂停画面
- 按需加载广告
- 更简单的实现
autoPlay设置:true(自动播放)
连播广告
当您的广告插播包含多个连续广告时,您可以根据设备功能采用三种方法:
- 单一广告播放器(资源受限的设备): 一次只能创建一个广告播放器来播放单个广告。这可能无法提供无缝或无间隙的过渡,仅建议内存或解码器受限的设备使用。
- 多个广告播放器(高性能设备): 使用两个广告播放器实例实现无缝的连播广告播放。提供连续广告之间的无缝过渡。需要足够的设备资源。
- 主要内容播放器重建(替代方法): 存储主要内容的当前播放位置。在上次广告播放期间重建主播放器。这样可以在资源受限的设备上创建两个独立的广告播放器。
CSAI面临的挑战
在实施CSAI时,请考虑以下挑战:
- 缓冲和延迟: 当按需加载广告(即时广告)时,暂停内容以加载广告可能会导致缓冲或延迟。为了解决这个问题,预缓冲的广告在实际展示时间之前缓冲广告内容,只在足够的缓冲时间后才暂停主要内容。
- 设备兼容性: 由于硬件功能(内存、解码器、处理能力)不同,不同设备的性能不一致。
- 单展示位渲染: 在渲染完主内容的最后一帧之后,广告必须从第一帧开始播放。在表面切换期间,不应出现丢帧或帧重叠的情况。
- 表面清除时的黑屏闪烁问题: 在共享表面模式下,调用
clearSurfaceHandle(JS) 或clearVideoView(无头)会清空显示缓冲区,导致在主内容最后一帧与广告第一帧之间出现明显的黑帧。建议使用双表面模式来规避。
表面模式
两种实现方法均支持两种表面模式,用于管理广告切换期间的视频输出。
单表面(共享)
主播放器和广告播放器共用同一个KeplerVideoSurfaceView。主播放器必须在广告播放器渲染表面前将其释放:
- JS方法: 在主播放器上调用
clearSurfaceHandle(surfaceHandle),然后在广告播放器上调用setSurfaceHandle(surfaceHandle)。 - 无头方法: 在主播放器会话中调用
clearVideoView(sessionId),然后在广告播放器会话中调用setVideoView(surfaceHandle, sessionId)。
双表面
每个播放器都有其专用的KeplerVideoSurfaceView。广告位通过zIndex定位,叠加在主内容区域之上。广告播放时,主播放器无需释放其表面。
表面视图布局 (JSX)
主内容区域使用zIndex: 1,广告位则使用zIndex: 2,从而使广告渲染在上方:
{/* 主内容区域 */}
<View style={{ position: 'absolute', top: 0, bottom: 0, left: 0, right: 0, zIndex: 1 }}>
<KeplerVideoSurfaceView
scalingmode='fit'
onSurfaceViewCreated={onSurfaceViewCreated}
onSurfaceViewDestroyed={onSurfaceViewDestroyed}
/>
</View>
{/* 广告位 — 播放广告时叠加主内容 */}
<View style={{ position: 'absolute', top: 0, bottom: 0, left: 0, right: 0, zIndex: 2 }}>
<KeplerVideoSurfaceView
scalingmode='fit'
onSurfaceViewCreated={onAdSurfaceViewCreated}
onSurfaceViewDestroyed={onAdSurfaceViewDestroyed}
/>
</View>
无广告播放时,广告位呈透明色,会显示主内容。广告播放器渲染的帧会覆盖底下的主内容。
display: 'none'来隐藏广告位。原生KeplerVideoSurfaceView需要先完成视图的挂载和布局,才能创建其表面句柄。处理不同宽高比的广告(双表面)
当广告和主内容的宽高比相同(例如均为16:9)时,广告位会直接叠加主内容。无需额外处理。
当两者的宽高比不同时,由于尺寸不匹配,非活动表面会在活动表面后方透出来。为避免此问题,可使用屏幕外渲染,将非活动表面移到屏幕外:
{/* 主内容区域:广告播放时移至屏幕外 */}
<View style={{
position: 'absolute', top: 0, bottom: 0, right: 0,
left: adSurfaceVisible ? -Dimensions.get('window').width * 1.2 : 0,
zIndex: adSurfaceVisible ? -1 : 1
}}>
<KeplerVideoSurfaceView scalingmode='fit' ... />
</View>
{/* 广告位:广告可见时在屏幕内移动 */}
<View style={{
position: 'absolute', top: 0, bottom: 0, right: 0,
left: adSurfaceVisible ? 0 : -Dimensions.get('window').width * 1.2,
zIndex: adSurfaceVisible ? 2 : -1
}}>
<KeplerVideoSurfaceView scalingmode='fit' ... />
</View>
要实现这一步,请先添加一个状态变量const [adSurfaceVisible, setAdSurfaceVisible] = useState(false),然后在广告开始时切换为true,在广告结束时切换为false。
广告时长强制实施
如果广告的定义时长小于实际媒体时长,您可以通过位置或时间更新侦听器,在正确的时间强制结束广告:
// JS方式:使用timeupdate事件
const updateAdTimer = () => {
if (adVideoPlayer.current && currentAdRef.current?.duration > 0) {
const currentTime = adVideoPlayer.current.currentTime || 0;
const adDuration = currentAdRef.current.duration;
if (currentTime >= adDuration) {
onAdEnded(); // 达到定义时长后强制结束广告
}
}
};
在无头方式中,使用在广告播放器会话上注册的位置侦听器,将当前播放位置与预期广告时长进行比对。
选择您的实现方法
Vega为CSAI提供了两种实现方法。选择与您的应用架构相匹配的方法。
JS方法
JS方法使用直接的React Native组件集成。VideoPlayer实例在用户界面组件层中创建和管理。应用程序可以直接访问W3C媒体API,并通过直接方法调用来控制播放。
┌─────────────────────────────────────────────────────────┐
│ React原生组件层 │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ 主播放器 │ │ 广告播放器 │ │
│ │ (VideoPlayer) │ │ (VideoPlayer) │ │
│ └──────────────────┘ └──────────────────┘ │
├─────────────────────────────────────────────────────────┤
│ KeplerVideoSurfaceView(共享或双表面) │
└─────────────────────────────────────────────────────────┘
在以下情况下使用此方法:
- 您的应用使用单线程React Native架构
- 您想直接访问W3C媒体API
- 您更偏好更简单的组件级播放器管理
程序包:@amazon-devices/react-native-w3cmedia
要实现这种方法,请参阅使用JS方法实现客户端广告插入。
无头方法
无头方法使用客户端-服务器架构。用户界面层中的IPlayerClient实例使用JavaScript接口 (JSI) 通过KeplerPlayerClientTurboModule与在单独服务层中管理的VideoPlayer实例进行通信,用于高性能进程间通信 (IPC)。
┌─────────────────────────────────────────────────────────┐
│ 用户界面层 (PlayerUI) │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ 主播放器 │ │ 广告播放器 │ │
│ │ 客户端 │ │ 客户端 │ │
│ │ (IPlayerClient) │ │ (IPlayerClient)│ │
│ └────────┬─────────┘ └─────────┬────────┘ │
│ │ │ │
│ │ KeplerPlayerClientTurboModule │
│ ▼ ▼ │
├─────────────────────────────────────────────────────────┤
│ IPC桥 (JSI) │
├─────────────────────────────────────────────────────────┤
│ │ KeplerPlayerServerTurboModule │
│ ▼ ▼ │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ PlayerServer │ │ PlayerServer │ │
│ │ 处理程序 │ │ 处理程序 │ │
│ └────────┬─────────┘ └─────────┬────────┘ │
│ │ │ │
│ 服务层(PlayerService - 无头JS) │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ 主播放器 │ │ 广告播放器 │ │
│ │ 会话 │ │ 会话 │ │
│ │ (VideoPlayer) │ │ (VideoPlayer) │ │
│ └──────────────────┘ └──────────────────┘ │
├─────────────────────────────────────────────────────────┤
│ KeplerVideoSurfaceView(共享或双表面) │
└─────────────────────────────────────────────────────────┘
在以下情况下使用此方法:
- 您的应用使用具有独立用户界面和服务层的多线程架构
- 您需要基于JSI的IPC的性能优势
- 您想用
PlayerSession进行基于会话的玩家管理
程序包: Vega播放器客户端和Vega播放器服务器
要实现这种方法,请参阅使用无头方法实现客户端广告插入。
相关内容
Last updated: 2026年5月28日

