ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

interactive_media_ads 接入实战:在 Flutter 中集成 IMA SDK 播放 VAST 视频广告

2026/9/18 4:37:46 拓冰建站 浏览量
interactive_media_ads 接入实战:在 Flutter 中集成 IMA SDK 播放 VAST 视频广告 interactive_media_ads 接入实战在 Flutter 中集成 IMA SDK 播放 VAST 视频广告【免费下载链接】packagesA collection of useful packages maintained by the Flutter team项目地址: https://gitcode.com/GitHub_Trending/pac/packagesinteractive_media_ads是 Flutter 官方插件仓库中由 Flutter 团队维护的广告插件它将 Google Interactive Media AdsIMASDK 的能力封装为 Dart API让 Flutter 应用可以轻松地向任意遵循 VAST 规范 为主线结合仓库中的 示例代码、核心 API 源码 与平台配置完整讲解从 Android 工程配置、五大核心组件到广告请求、事件处理、资源释放的端到端接入流程。读完本文你将能够在自己的 Flutter 应用中实现内容视频 前置/中插/后置广告的完整播放体验。IMA client-side 模式的核心思想IMA 插件采用的是client-side客户端集成模式广告请求由 App 直接向广告服务器发出App 保留对内容视频播放的完全控制权而 SDK 只负责广告的请求与播放。广告播放时SDK 会使用一个独立于内容播放器的广告播放器将其定位在内容视频之上广告结束后再交还控制权给内容播放器。这意味着接入 IMA 不会改变你现有的内容播放方案——你依然使用 video_player 之类的插件播放正片IMA 插件则像叠加层一样处理广告。README 中的一句话概括了这一分工With IMA client-side SDKs, you maintain control of content video playback, while the SDK handles ad playback.平台支持情况平台支持版本AndroidSDK 24iOS13.0注意README 原文声明Background Audio ads后台音频广告与 Google Dynamic Ad InsertionGoogle 动态广告插入方法目前不受支持。五大核心组件IMA client-side 的骨架README 指出实现 IMA client-side 涉及五个主要 SDK 组件它们也是本插件 Dart API 的核心类组件Dart 类职责AdDisplayContainerad_display_container.dart广告渲染的容器 Widget负责承载广告视图并处理广告点击AdsLoaderads_loader.dart请求广告并处理广告请求响应的各类事件同一时间只能实例化一个 AdsLoader可在页面生命周期内复用AdsRequestads_request.dart定义一次广告请求指定 VAST 广告标签ad tagURL、广告尺寸等附加参数AdsManagerads_loader.dart同文件持有广告请求的响应结果控制广告播放并监听 SDK 抛出的广告事件AdsManagerDelegateads_manager_delegate.dart处理广告/流初始化与播放过程中发生的广告事件和错误从源码结构看这些类均采用平台接口 平台实现的分层设计Platform*系列类在 platform_interface 下Android/iOS 各有独立实现目录 android 与 ios并且插件通过 Pigeon 生成平台通道代码见 pigeons 目录因此你可以在 Dart 层统一调用而不必关心原生差异。第一步Android 工程配置仅 Android 需要如果目标平台包含 Android需要完成两项配置若只构建 iOS 可跳过本节。1. 更新 AndroidManifest 权限在android/app/src/main/AndroidManifest.xml中为 IMA SDK 添加请求广告所需的用户权限与仓库示例 AndroidManifest.xml 一致manifest xmlns:androidhttp://schemas.android.com/apk/res/android !-- Required permissions for the IMA SDK -- uses-permission android:nameandroid.permission.INTERNET/ uses-permission android:nameandroid.permission.ACCESS_NETWORK_STATE/ /manifest2. 更新 App 级 Gradle 配置启用库脱糖IMA SDK 要求启用library desugaring库脱糖。需要在android/app/build.gradle.kts中设置coreLibraryDesugaringEnabled true并将coreLibraryDesugaring com.android.tools:desugar_jdk_libs:2.1.5加入依赖示例见 build.gradle.ktsandroid { // ··· compileOptions { isCoreLibraryDesugaringEnabled true // ··· } // ··· } // ··· dependencies { coreLibraryDesugaring(com.android.tools:desugar_jdk_libs:2.1.5) // ··· }关于脱糖后可用的 Java 11 API如java.nio相关接口可查阅 Android 开发者文档中Java 11 APIs available through desugaring的兼容性说明。简单来说脱糖让低版本 AndroidSDK 24也能运行使用较新 Java API 的 IMA SDK。第二步添加依赖与导入在pubspec.yaml中加入interactive_media_ads与video_player两个插件。仓库中该插件的 pubspec.yaml 显示其当前版本为0.3.017要求 Dart SDK^3.12.0、Flutter3.44.0。然后在 Dart 文件中导入import package:interactive_media_ads/interactive_media_ads.dart; import package:video_player/video_player.dart;interactive_media_ads.dart作为插件入口 统一导出了全部公开 API包括五个核心组件类、AdEventType等事件枚举以及AdError、AdsLoadErrorData、CompanionAdSlotSize等辅助类型。第三步创建承载广告与内容的 Widget创建一个StatefulWidget负责同时展示广告与播放内容。仓库示例 readme_example.dart 中的AdExampleWidget是一个完整范例其状态类需要维护以下几类关键对象class _AdExampleWidgetState extends StateAdExampleWidget with WidgetsBindingObserver { // IMA 示例广告标签前置 中插 后置的单条 inline 视频广告 static const String _adTagUrl https://pubads.g.doubleclick.net/gampad/ads?iu/21775744923/external/vmap_ad_samplessz640x480cust_paramssample_ar%3Dpremidpostciu_szs300x250gdfp_req1ad_rule1outputvmapunviewed_position_start1envvpimplscmsid496vidshort_onecuecorrelator; // AdsLoader 暴露 requestAds 方法 late final AdsLoader _adsLoader; // AdsManager 控制广告播放并监听广告事件 AdsManager? _adsManager; // 是否应显示内容视频广告播放期间隐藏内容播放器 bool _shouldShowContentVideo false; // 控制内容视频播放器 late final VideoPlayerController _contentVideoController; // 周期性向 SDK 上报内容视频播放进度 Timer? _contentProgressTimer; // 向 SDK 提供内容视频当前播放进度支持中插广告mid-roll所必需 final ContentProgressProvider _contentProgressProvider ContentProgressProvider(); // ··· }代码中的_adTagUrl是 IMA 官方提供的 VMAP 示例广告标签对应 pre-/mid-/post-roll 三种插播位实际项目中请替换为你自己的广告标签 URL。广告标签可以是单个 VAST 标签也可以是 VMAP 或 ad rules 响应具体取决于广告服务器支持。第四步添加广告播放器与内容播放器实例化用于播放广告的AdDisplayContainer以及用于播放内容的VideoPlayerController。这是整个接入流程中最核心的一段代码它串起了AdDisplayContainer → AdsLoader → AdsManager → AdsManagerDelegate的完整调用链late final AdDisplayContainer _adDisplayContainer AdDisplayContainer( onContainerAdded: (AdDisplayContainer container) { _adsLoader AdsLoader( container: container, onAdsLoaded: (OnAdsLoadedData data) { final AdsManager manager data.manager; _adsManager data.manager; manager.setAdsManagerDelegate( AdsManagerDelegate( onAdEvent: (AdEvent event) { debugPrint(OnAdEvent: ${event.type} ${event.adData}); switch (event.type) { case AdEventType.loaded: manager.start(); case AdEventType.contentPauseRequested: _pauseContent(); case AdEventType.contentResumeRequested: _resumeContent(); case AdEventType.allAdsCompleted: manager.destroy(); _adsManager null; case AdEventType.clicked: case AdEventType.complete: case _: } }, onAdErrorEvent: (AdErrorEvent event) { debugPrint(AdErrorEvent: ${event.error.message}); _resumeContent(); }, ), ); manager.init(settings: AdsRenderingSettings(enablePreloading: true)); }, onAdsLoadError: (AdsLoadErrorData data) { debugPrint(OnAdsLoadError: ${data.error.message}); _resumeContent(); }, ); // 广告只有在 AdDisplayContainer 被添加到原生 View 层级之后才能被请求 _requestAds(container); }, ); override void initState() { super.initState(); // ··· _contentVideoController VideoPlayerController.networkUrl( Uri.parse(https://storage.googleapis.com/gvabox/media/samples/stock.mp4), ) ..addListener(() { if (_contentVideoController.value.isCompleted) { _adsLoader.contentComplete(); } setState(() {}); }) ..initialize().then((_) { // 确保视频初始化后立即显示首帧即使在播放按钮被按下之前 setState(() {}); }); }这段代码蕴含了几个必须理解的要点onContainerAdded回调是起点AdDisplayContainer是一个真正的Widget继承自StatelessWidget见 ad_display_container.dart当它的原生视图被挂载到平台视图层级后才会触发onContainerAdded。在容器被加入视图层级之前不能发起广告请求因此AdsLoader的创建与requestAds调用都放在这个回调里。AdsLoader只实例化一次如 README 所述同时只应存在一个AdsLoader可在页面生命周期内复用。它的构造参数需要container、onAdsLoaded广告加载成功、onAdsLoadError加载失败三个必填项另有可选的ImaSettings通用 SDK 设置。广告成功加载后拿到AdsManageronAdsLoaded中的OnAdsLoadedData.manager即本次广告响应的AdsManager。此时需要依次完成setAdsManagerDelegate(...)注册事件委托 →manager.init(settings: ...)初始化广告体验 → 在loaded事件中manager.start()开始播放。示例中通过AdsRenderingSettings(enablePreloading: true)开启预加载让 SDK 在init时就加载广告素材详见后文渲染设置小节。内容播放进度同步ContentProgressProvider向 SDK 上报内容视频的播放进度这是中插广告mid-roll按 cue point 触发的前提同时监听内容播放器完成事件在播放完毕后调用_adsLoader.contentComplete()通知 SDK 内容已结束源码见 ads_loader.dart。第五步实现build方法build返回同时包含广告播放器与内容播放器的 Widget。广告显示容器必须保持常驻屏幕——它既要在广告加载前就存在也不能在两次广告之间被移除因为它还负责处理广告点击override Widget build(BuildContext context) { return Scaffold( body: Center( child: SizedBox( width: 300, child: !_contentVideoController.value.isInitialized ? Container() : AspectRatio( aspectRatio: _contentVideoController.value.aspectRatio, child: Stack( children: Widget[ // 显示容器必须在广告加载前就出现在屏幕上 // 且不能在广告之间被移除。它负责处理广告点击。 _adDisplayContainer, if (_shouldShowContentVideo) VideoPlayer(_contentVideoController), ], ), ), ), ), floatingActionButton: _contentVideoController.value.isInitialized _shouldShowContentVideo ? FloatingActionButton( onPressed: () { setState(() { _contentVideoController.value.isPlaying ? _contentVideoController.pause() : _contentVideoController.play(); }); }, child: Icon(_contentVideoController.value.isPlaying ? Icons.pause : Icons.play_arrow), ) : null, ); }布局上_adDisplayContainer与内容VideoPlayer被放入同一个Stack广告播放时_shouldShowContentVideo false隐藏内容视频广告浮层自然覆盖其上内容播放时再显示VideoPlayer。_shouldShowContentVideo的切换完全由广告事件驱动见下一节。第六步请求广告与内容播放控制_requestAds通过AdsLoader.requestAds发起请求并传入AdsRequest与进度提供器_resumeContent/_pauseContent则负责在广告打断内容播放时正确切换两个播放器Futurevoid _requestAds(AdDisplayContainer container) { return _adsLoader.requestAds( AdsRequest(adTagUrl: _adTagUrl, contentProgressProvider: _contentProgressProvider), ); } Futurevoid _resumeContent() async { setState(() { _shouldShowContentVideo true; }); if (_adsManager ! null) { _contentProgressTimer Timer.periodic(const Duration(milliseconds: 200), ( Timer timer, ) async { if (_contentVideoController.value.isInitialized) { final Duration? progress await _contentVideoController.position; if (progress ! null) { await _contentProgressProvider.setProgress( progress: progress, duration: _contentVideoController.value.duration, ); } } }); } await _contentVideoController.play(); } Futurevoid _pauseContent() { setState(() { _shouldShowContentVideo false; }); _contentProgressTimer?.cancel(); _contentProgressTimer null; return _contentVideoController.pause(); }这里有两个值得注意的细节进度上报间隔Timer.periodic以200ms为间隔调用ContentProgressProvider.setProgress。源码注释明确指出使用Timer周期性上报时推荐 200ms 间隔见 content_progress_provider.dart既保证 cue point 触发的精度又不会给 UI 线程带来明显负担。恢复内容时的时序先恢复显示内容视频再重启进度上报定时器最后调用play()。这样广告结束瞬间不会出现黑屏或进度断档。AdsRequest 参数详解AdsRequest是定义一次广告请求的对象。除了必填的adTagUrl源码 ads_request.dart 还暴露了丰富的可选参数参数类型说明adTagUrlString必填广告标签 URL指向 VAST/VMAP/ad rules 广告服务器contentProgressProviderContentProgressProvider?内容进度提供器用于按 cue point 调度广告插播中插广告必需adWillAutoPlaybool?通知 SDK内容与广告是由用户操作启动还是自动播放adWillPlayMutedbool?通知 SDK内容与广告是否静音启动continuousPlaybackbool?通知 SDK内容视频是否像电视广播一样连续播放contentDurationDuration?待展示内容的时长contentKeywordsListString?描述内容的关键词contentTitleString?内容的标题liveStreamPrefetchMaxWaitTimeDuration?调用requestAds后、请求广告标签 URL 前的最长等待时间vastLoadTimeoutDuration?单个 VAST wrapper 的加载超时时间此外AdsRequest还提供了AdsRequest.withAdsResponse(...)构造方法可以直接传入一段canned ads response预置的 VAST/VMAP/ad rules 响应字符串代替网络请求——这在测试和离线演示场景中非常实用二者共享除adTagUrl/adsResponse外的全部参数。事件驱动模型AdEventType 与 AdsManager 控制方法广告的整个生命周期都由事件驱动。示例中AdsManagerDelegate的两个回调onAdEvent/onAdErrorEvent覆盖了广告事件与错误事件两类通知定义见 ads_manager_delegate.dart。AdEventType枚举完整定义见 platform_ad_event.dart包含 30 种事件按用途可归为几类生命周期核心事件loadedVAST 响应已收到此时应start()、started、complete、allAdsCompleted响应中所有有效广告播放完毕此时应destroy()并置空_adsManager、skipped、paused、resumed内容协同事件contentPauseRequested广告即将覆盖内容应暂停内容播放、contentResumeRequested广告结束应恢复内容播放广告插播ad break事件adBreakStarted、adBreakEnded、adBreakReadyVMAP/ad rules 广告插播就绪、adBreakFetchError广告插播未能播放任何广告、adPeriodStarted、adPeriodEnded、cuepointsChanged播放进度事件firstQuartile、midpoint、thirdQuartile、adProgress可用来实现倒计时 UI交互事件clicked、tapped、iconTapped、iconFallbackImageClosed、skippableStateChanged其他adBuffering、log、unknown等每个AdEvent还携带ad关联的广告对象与adData额外的键值数据方便在事件回调中做埋点或自定义 UI。AdsManager提供的控制方法见 ads_loader.dart覆盖了广告播放的完整控制面init({AdsRenderingSettings? settings})使用默认或自定义渲染设置初始化广告体验start()开始播放广告pause()/resume()暂停/恢复当前广告skip()跳过当前广告仅在 IMA 未渲染跳过广告按钮时生效discardAdBreak()放弃当前广告插播并恢复内容若无当前广告则放弃下一个广告插播destroy()停止广告及所有追踪并释放为播放该广告加载的全部资源adCuePoints属性广告插播计划的内容时间偏移列表单条广告或无插播时为空生命周期处理后台切换时的广告行为完整的接入还需要处理 App 前后台切换。仓库示例通过WidgetsBindingObserver监听AppLifecycleState变化来暂停/恢复广告这段逻辑在 README 正文中未展开但完整存在于 readme_example.dartoverride void didChangeAppLifecycleState(AppLifecycleState state) { switch (state) { case AppLifecycleState.resumed: if (!_shouldShowContentVideo) { _adsManager?.resume(); } case AppLifecycleState.inactive: // Android 上只能在 inactive 状态暂停广告视频播放器 // 因为它对应 Activity.onPause。该状态在 resume 前也会触发 // 因此只有在 App 即将进入后台时才暂停广告。 if (!_shouldShowContentVideo _lastLifecycleState AppLifecycleState.resumed) { _adsManager?.pause(); } case AppLifecycleState.hidden: case AppLifecycleState.paused: case AppLifecycleState.detached: } _lastLifecycleState state; }要点仅当当前正在播放广告_shouldShowContentVideo false时才操作_adsManager且 Android 平台必须在inactive状态暂停广告对应Activity.onPause时机。同时别忘记在initState中WidgetsBinding.instance.addObserver(this)、在dispose中removeObserver(this)。第七步释放资源广告与内容播放结束后需要释放内容播放器并销毁AdsManageroverride void dispose() { super.dispose(); _contentProgressTimer?.cancel(); _contentVideoController.dispose(); _adsManager?.destroy(); // ··· }dispose中依次取消进度上报定时器 → 释放VideoPlayerController→ 调用_adsManager?.destroy()销毁广告管理器释放广告相关资源。完成这一步整个接入流程即告结束——正如 README 所说Thats it! Youre now requesting and displaying ads with the IMA SDK.进阶配置AdsRenderingSettings 与 Companion AdsAdsRenderingSettingsAdsRenderingSettings控制广告的渲染行为构造参数与说明如下见 ads_rendering_settings.dart参数默认值说明bitratenull由 SDK 按当前网速选择最大推荐码率单位 kbit/sSDK 会选择低于该值或最接近的媒体enablePreloadingnull平台决定设为true时SDK 会在AdsManager.init时指示播放器加载广告素材从而可在start()之前的任意时间点预加载loadVideoTimeoutDuration(seconds: 8)媒体加载超时时间仅适用于 client-side SDKmimeTypesnull平台决定线性广告视频 MIME 类型优先级列表playAdsAfterTimenull仅播放晚于该时间的广告插播严格晚于例如设为 15s 会忽略恰好安排在 15s 的插播uiElementsnull平台决定SDK 渲染的广告 UI 元素集合部分广告可能忽略个别元素修改示例中AdsRenderingSettings(enablePreloading: true)即开启广告素材预加载通常能减少用户等待广告起播的时间。Companion Ads伴随广告AdDisplayContainer还接受companionSlots参数IterableCompanionAdSlot与layoutDirection参数广告容器内部布局方向默认TextDirection.ltr用于在内容页面中渲染与视频广告配对的伴随广告位如横幅。CompanionAdSlot及相关尺寸类型CompanionAdSlotSizeFixed、CompanionAdSlotSizeFluid均由插件公开导出相关单元测试见 test/companion_ad_slot_test.dart。从源码与测试进一步验证如果你希望深入理解插件的实现仓库提供了完整的源码与测试作为参考Dart API 层lib/src 下集中了所有公开类platform_interface子目录定义平台抽象接口android/ios子目录分别持有 Pigeon 生成的双端平台实现interactive_media_ads.g.dart 与 interactive_media_ads.g.dart原生层Android 端为 Kotlin 实现android/src/main/kotlin含 37 个.kt文件iOS 端为 Swift 实现ios/interactive_media_ads/SourcesPigeon 定义pigeons/interactive_media_ads_android.dart 与 pigeons/interactive_media_ads_ios.dart 是跨平台通道代码的单一事实来源测试覆盖test 下针对AdsLoader、AdsManager、AdDisplayContainer、ContentProgressProvider、ImaSettings、CompanionAdSlot等均提供了 Android/iOS 双端单元测试如 ads_loader_test.dart另有 integration_test 端到端测试完整可运行示例example/lib/readme_example.dart本文所有代码片段即出自于此、example/lib/video_ad_example_screen.dart 与 example/lib/main.dart小结接入interactive_media_ads的完整心智模型可以概括为一个容器AdDisplayContainer负责承载广告视图 → 一个加载器AdsLoader负责向 VAST 兼容的广告服务器请求广告 → 一个管理器AdsManager持有响应并控制播放 → 一个委托AdsManagerDelegate接收事件并驱动内容播放器的暂停/恢复再辅以ContentProgressProvider上报内容进度以支持中插广告。Android 侧只需完成 Manifest 权限与 Gradle 脱糖两项配置即可与video_player无缝配合为你的 Flutter 视频应用接入完整的 pre-roll / mid-roll / post-roll 广告能力。【免费下载链接】packagesA collection of useful packages maintained by the Flutter team项目地址: https://gitcode.com/GitHub_Trending/pac/packages创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考