Flutter互动媒体广告插件interactive_media_ads的使用

发布于 1周前 作者 wuwangju 来自 Flutter

Flutter互动媒体广告插件interactive_media_ads的使用

简介

interactive_media_ads 是一个用于在Flutter应用中集成Google Interactive Media Ads (IMA) SDK的插件。通过这个插件,你可以轻松地将多媒体广告集成到你的应用中。IMA SDK可以从任何符合VAST标准的广告服务器请求广告,并管理广告的播放。

支持平台

  • Android: SDK 21+
  • iOS: 12.0+

注意事项

  • 目前不支持伴生广告(Companion ads)、背景音频广告(Background Audio ads)和Google动态广告插入(Google Dynamic Ad Insertion)。

IMA客户端侧概述

实现IMA客户端侧涉及五个主要的SDK组件:

  1. AdDisplayContainer: 广告渲染的容器对象。
  2. AdsLoader: 请求广告并处理广告请求响应事件。你应该只实例化一个广告加载器,并在整个应用程序生命周期中重用它。
  3. AdsRequest: 定义广告请求的对象。广告请求指定VAST广告标签的URL以及其他参数,如广告尺寸。
  4. AdsManager: 包含广告请求的响应,控制广告播放,并监听SDK发出的广告事件。
  5. AdsManagerDelegate: 处理广告或流初始化和播放期间发生的事件和错误。

使用步骤

1. 添加Android所需权限

如果在Android上构建,需要在 android/app/src/main/AndroidManifest.xml 中添加IMA SDK所需的用户权限:

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- Required permissions for the IMA SDK -->
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
</manifest>
2. 添加导入语句

在Dart文件中添加 interactive_media_adsvideo_player 插件的导入语句。这两个插件应该已经在 pubspec.yaml 中添加。

import 'package:interactive_media_ads/interactive_media_ads.dart';
import 'package:video_player/video_player.dart';
3. 创建一个新的Widget

创建一个新的 StatefulWidget 来处理广告显示和内容播放。

/// Example widget displaying an Ad before a video.
class AdExampleWidget extends StatefulWidget {
  /// Constructs an [AdExampleWidget].
  const AdExampleWidget({super.key});

  [@override](/user/override)
  State<AdExampleWidget> createState() => _AdExampleWidgetState();
}

class _AdExampleWidgetState extends State<AdExampleWidget> with WidgetsBindingObserver {
  // IMA sample tag for a pre-, mid-, and post-roll, single inline video ad.
  static const String _adTagUrl =
      'https://pubads.g.doubleclick.net/gampad/ads?iu=/21775744923/external/vmap_ad_samples&sz=640x480&cust_params=sample_ar%3Dpremidpost&ciu_szs=300x250&gdfp_req=1&ad_rule=1&output=vmap&unviewed_position_start=1&env=vp&impl=s&cmsid=496&vid=short_onecue&correlator=';

  // The AdsLoader instance exposes the request ads method.
  late final AdsLoader _adsLoader;

  // AdsManager exposes methods to control ad playback and listen to ad events.
  AdsManager? _adsManager;

  // Whether the widget should be displaying the content video. The content
  // player is hidden while Ads are playing.
  bool _shouldShowContentVideo = false;

  // Controls the content video player.
  late final VideoPlayerController _contentVideoController;

  // Periodically updates the SDK of the current playback progress of the
  // content video.
  Timer? _contentProgressTimer;

  // Provides the SDK with the current playback progress of the content video.
  // This is required to support mid-roll ads.
  final ContentProgressProvider _contentProgressProvider = ContentProgressProvider();

  [@override](/user/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>[
                      // The display container must be on screen before any Ads can be
                      // loaded and can't be removed between ads. This handles clicks for
                      // ads.
                      _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,
    );
  }
}
4. 添加视频播放器

实例化 AdDisplayContainer 用于播放广告,以及 VideoPlayerController 用于播放内容。

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();
      },
    );

    // Ads can't be requested until the `AdDisplayContainer` has been added to
    // the native View hierarchy.
    _requestAds(container);
  },
);

[@override](/user/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((_) {
      // Ensure the first frame is shown after the video is initialized, even before the play button has been pressed.
      setState(() {});
    });
}
5. 实现 build 方法

返回一个包含广告播放器和内容播放器的 Widget

[@override](/user/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>[
                    // The display container must be on screen before any Ads can be
                    // loaded and can't be removed between ads. This handles clicks for
                    // ads.
                    _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,
  );
}
6. 请求广告

处理广告请求并添加事件监听器来控制内容的显示或隐藏。

Future<void> _requestAds(AdDisplayContainer container) {
  return _adsLoader.requestAds(AdsRequest(
    adTagUrl: _adTagUrl,
    contentProgressProvider: _contentProgressProvider,
  ));
}

Future<void> _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();
}

Future<void> _pauseContent() {
  setState(() {
    _shouldShowContentVideo = false;
  });
  _contentProgressTimer?.cancel();
  _contentProgressTimer = null;
  return _contentVideoController.pause();
}
7. 释放资源

释放内容播放器并销毁 AdsManager

[@override](/user/override)
void dispose() {
  super.dispose();
  _contentProgressTimer?.cancel();
  _contentVideoController.dispose();
  _adsManager?.destroy();
  WidgetsBinding.instance.removeObserver(this);
}

完整示例代码

// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:interactive_media_ads/interactive_media_ads.dart';
import 'package:video_player/video_player.dart';

void main() {
  runApp(const MaterialApp(home: AdExampleWidget()));
}

/// Example widget displaying an Ad before a video.
class AdExampleWidget extends StatefulWidget {
  /// Constructs an [AdExampleWidget].
  const AdExampleWidget({super.key});

  [@override](/user/override)
  State<AdExampleWidget> createState() => _AdExampleWidgetState();
}

class _AdExampleWidgetState extends State<AdExampleWidget> with WidgetsBindingObserver {
  // IMA sample tag for a pre-, mid-, and post-roll, single inline video ad.
  static const String _adTagUrl =
      'https://pubads.g.doubleclick.net/gampad/ads?iu=/21775744923/external/vmap_ad_samples&sz=640x480&cust_params=sample_ar%3Dpremidpost&ciu_szs=300x250&gdfp_req=1&ad_rule=1&output=vmap&unviewed_position_start=1&env=vp&impl=s&cmsid=496&vid=short_onecue&correlator=';

  // The AdsLoader instance exposes the request ads method.
  late final AdsLoader _adsLoader;

  // AdsManager exposes methods to control ad playback and listen to ad events.
  AdsManager? _adsManager;

  // Last state received in `didChangeAppLifecycleState`.
  AppLifecycleState _lastLifecycleState = AppLifecycleState.resumed;

  // Whether the widget should be displaying the content video. The content
  // player is hidden while Ads are playing.
  bool _shouldShowContentVideo = false;

  // Controls the content video player.
  late final VideoPlayerController _contentVideoController;

  // Periodically updates the SDK of the current playback progress of the
  // content video.
  Timer? _contentProgressTimer;

  // Provides the SDK with the current playback progress of the content video.
  // This is required to support mid-roll ads.
  final ContentProgressProvider _contentProgressProvider = ContentProgressProvider();

  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();
        },
      );

      // Ads can't be requested until the `AdDisplayContainer` has been added to
      // the native View hierarchy.
      _requestAds(container);
    },
  );

  [@override](/user/override)
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);

    _contentVideoController = VideoPlayerController.networkUrl(
      Uri.parse(
        'https://storage.googleapis.com/gvabox/media/samples/stock.mp4',
      ),
    )
      ..addListener(() {
        if (_contentVideoController.value.isCompleted) {
          _adsLoader.contentComplete();
        }
        setState(() {});
      })
      ..initialize().then((_) {
        // Ensure the first frame is shown after the video is initialized, even before the play button has been pressed.
        setState(() {});
      });
  }

  [@override](/user/override)
  void didChangeAppLifecycleState(AppLifecycleState state) {
    switch (state) {
      case AppLifecycleState.resumed:
        if (!_shouldShowContentVideo) {
          _adsManager?.resume();
        }
      case AppLifecycleState.inactive:
        // Pausing the Ad video player on Android can only be done in this state
        // because it corresponds to `Activity.onPause`. This state is also
        // triggered before resume, so this will only pause the Ad if the app is
        // in the process of being sent to the background.
        if (!_shouldShowContentVideo && _lastLifecycleState == AppLifecycleState.resumed) {
          _adsManager?.pause();
        }
      case AppLifecycleState.hidden:
      case AppLifecycleState.paused:
      case AppLifecycleState.detached:
    }
    _lastLifecycleState = state;
  }

  Future<void> _requestAds(AdDisplayContainer container) {
    return _adsLoader.requestAds(AdsRequest(
      adTagUrl: _adTagUrl,
      contentProgressProvider: _contentProgressProvider,
    ));
  }

  Future<void> _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();
  }

  Future<void> _pauseContent() {
    setState(() {
      _shouldShowContentVideo = false;
    });
    _contentProgressTimer?.cancel();
    _contentProgressTimer = null;
    return _contentVideoController.pause();
  }

  [@override](/user/override)
  void dispose() {
    super.dispose();
    _contentProgressTimer?.cancel();
    _contentVideoController.dispose();
    _adsManager?.destroy();
    WidgetsBinding.instance.removeObserver(this);
  }

  [@override](/user/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>[
                      // The display container must be on screen before any Ads can be
                      // loaded and can't be removed between ads. This handles clicks for
                      // ads.
                      _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,
    );
  }
}

更多关于Flutter互动媒体广告插件interactive_media_ads的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter互动媒体广告插件interactive_media_ads的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是如何在Flutter项目中集成和使用interactive_media_ads插件的示例代码。请注意,interactive_media_ads是一个假定的插件名称,因为实际中可能存在不同的插件用于实现互动媒体广告。不过,我将提供一个通用的模板,你可以根据实际的插件文档进行调整。

首先,确保你已经在pubspec.yaml文件中添加了插件依赖项(这里以interactive_media_ads为例,实际名称可能会有所不同):

dependencies:
  flutter:
    sdk: flutter
  interactive_media_ads: ^x.y.z  # 替换为实际版本号

然后,运行flutter pub get来安装依赖项。

接下来,在你的Flutter项目中,你可以按照以下步骤使用interactive_media_ads插件:

  1. 导入插件

在你的Dart文件中(比如main.dart),导入插件:

import 'package:interactive_media_ads/interactive_media_ads.dart';
  1. 初始化广告

通常,你需要一个广告单元ID(Ad Unit ID),这是从广告提供商那里获取的。以下是一个初始化广告的示例:

void main() {
  runApp(MyApp());

  // 假设在应用程序启动时初始化广告
  _initializeInteractiveMediaAd();
}

void _initializeInteractiveMediaAd() {
  // 创建一个InteractiveMediaAd实例
  final InteractiveMediaAd interactiveMediaAd = InteractiveMediaAd(
    adUnitId: 'YOUR_AD_UNIT_ID',  // 替换为你的广告单元ID
    listener: (InteractiveMediaAdEvent event, Map<String, dynamic> info) {
      // 处理广告事件,比如加载完成、点击、关闭等
      if (event == InteractiveMediaAdEvent.loaded) {
        print('广告加载完成');
        // 可以在这里显示广告
      } else if (event == InteractiveMediaAdEvent.clicked) {
        print('广告被点击');
      } else if (event == InteractiveMediaAdEvent.closed) {
        print('广告已关闭');
      }
      // 其他事件处理...
    },
  );

  // 加载广告
  interactiveMediaAd.load();
}
  1. 显示广告

在适当的时候(比如用户完成某个任务或达到某个页面),显示广告:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Interactive Media Ads Example'),
        ),
        body: Center(
          child: ElevatedButton(
            onPressed: () {
              // 假设interactiveMediaAd已经在某处被初始化并加载
              // 显示广告(这里需要确保广告已经加载完成)
              interactiveMediaAd.show();
            },
            child: Text('显示广告'),
          ),
        ),
      ),
    );
  }
}
  1. 处理广告生命周期

确保在应用程序的生命周期中适当地管理广告(比如暂停和恢复广告)。这通常涉及到在AppLifecycleState变化时调用插件提供的方法。

@override
void didChangeAppLifecycleState(AppLifecycleState state) {
  super.didChangeAppLifecycleState(state);
  if (state == AppLifecycleState.paused) {
    // 应用进入后台时暂停广告(如果插件支持)
    interactiveMediaAd.pause();
  } else if (state == AppLifecycleState.resumed) {
    // 应用回到前台时恢复广告
    interactiveMediaAd.resume();
  }
}

请注意,上述代码是一个模板,具体的API和方法名称可能会根据你使用的实际插件而有所不同。务必参考插件的官方文档来获取最准确的信息。

回到顶部