Flutter plugin for the Interactive Media Ads SDKs.
IMA SDKs make it easy to integrate multimedia ads into your websites and apps. IMA SDKs can request ads from any VAST-compliant ad server and manage ad playback in your apps. With IMA client-side SDKs, you maintain control of content video playback, while the SDK handles ad playback. Ads play in a separate video player positioned on top of the app's content video player.
Android | iOS | |
---|---|---|
Support | SDK 21+ | 12.0+ |
NOTE:
- The initial release for this package supports linear pre-roll video ads on iOS and Android platforms.
- Companion ads, Background Audio ads and Google Dynamic Ad Insertion methods are currently not supported.
Implementing IMA client-side involves five main SDK components, which are demonstrated in this guide:
- AdDisplayContainer: A container object where ads are rendered.
- AdsLoader: Requests ads and handles events from ads request responses. You should only instantiate one ads loader, which can be reused throughout the life of the application.
- AdsRequest: An object that defines an ads request. Ads requests specify the URL for the VAST ad tag, as well as additional parameters, such as ad dimensions.
- AdsManager: Contains the response to the ads request, controls ad playback, and listens for ad events fired by the SDK.
- AdsManagerDelegate: Handles ad events and errors that occur during ad or stream initialization and playback.
This guide demonstrates how to integrate the IMA SDK into a new Widget
using the video_player
plugin to display content.
If building on Android, add the user permissions required by the IMA SDK for requesting ads in
android/app/src/main/AndroidManifest.xml
.
<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"/>
Add the import statements for the interactive_media_ads
and video_player. Both plugins should
already be added to your pubspec.yaml
.
import 'package:interactive_media_ads/interactive_media_ads.dart';
import 'package:video_player/video_player.dart';
Create a new StatefulWidget that handles displaying Ads and playing content.
/// Example widget displaying an Ad before a video.
class AdExampleWidget extends StatefulWidget {
/// Constructs an [AdExampleWidget].
const AdExampleWidget({super.key});
@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. See more IMA sample
// tags at https://developers.google.com/interactive-media-ads/docs/sdks/html5/client-side/tags
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 = true;
// 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
Widget build(BuildContext context) {
// ···
}
}
Instantiate the AdDisplayContainer for playing Ads and the VideoPlayerController for playing content.
late final AdDisplayContainer _adDisplayContainer = AdDisplayContainer(
onContainerAdded: (AdDisplayContainer container) {
// Ads can't be requested until the `AdDisplayContainer` has been added to
// the native View hierarchy.
_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((_) {
// Ensure the first frame is shown after the video is initialized, even before the play button has been pressed.
setState(() {});
});
}
Return a Widget
that contains the ad player and the content player.
@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,
);
}
Handle requesting ads and add event listeners to handle when content should be displayed or hidden.
Future<void> _requestAds(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();
},
onAdsLoadError: (AdsLoadErrorData data) {
debugPrint('OnAdsLoadError: ${data.error.message}');
_resumeContent();
},
);
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();
}
Dispose the content player and destroy the AdsManager.
@override
void dispose() {
super.dispose();
_contentProgressTimer?.cancel();
_contentVideoController.dispose();
_adsManager?.destroy();
// ···
}
That's it! You're now requesting and displaying ads with the IMA SDK. To learn about additional SDK features, see the API reference.
For information on contributing to this plugin, see CONTRIBUTING.md
.