借助 IMA SDK,您可以轻松地将多媒体广告集成到您的网站和应用中。IMA SDK 可以从任何符合 VAST 标准的广告服务器请求广告,并在您的应用中管理广告播放。借助 IMA DAI SDK,应用可以为广告和内容视频(VOD 或直播内容)发出视频流请求。然后,SDK 会返回一个组合的视频流,因此您无需管理应用中的广告和内容视频之间的切换。
本指南介绍了如何将 IMA SDK 集成到一个简单的视频播放器应用中。如果您想要查看或遵循已完成的示例集成,请从 GitHub 下载基本示例。
IMA DAI 概览
实现 IMA DAI 涉及到四个主要 SDK 组件,本指南对此进行了演示:
StreamDisplayContainer
:一种容器对象,它位于视频播放元素之上,用于存放广告界面元素。AdsLoader
:用于请求数据流并处理由数据流请求响应对象触发的事件的对象。 您只能实例化一个广告加载器,而且该加载器可以在应用的整个生命周期中重复使用。StreamRequest
:定义流请求的对象。视频流请求可以用于视频点播或直播。请求会指定内容 ID,以及 API 密钥或身份验证令牌和其他参数。StreamManager
:处理动态广告插播流以及与 DAI 后端互动的对象。流管理器还会处理跟踪 ping,并将流和广告事件转发给发布商。
前提条件
- Android Studio
- 您将集成 SDK 的示例应用
下载并运行示例视频播放器应用
示例应用提供了一个可播放 HLS 视频的正常运行视频播放器。您可以从这里开始集成 IMA Android SDK 的 DAI 功能。
- 下载示例视频播放器应用并解压缩。
- 启动 Android Studio,然后选择 Open an existing Android Studio project;如果 Android Studio 已在运行,请依次选择 File > New > Import Project。然后选择
SampleVideoPlayer/build.gradle
。 - 依次选择 Tools > Android > Sync Project with Gradle Files 以运行 Gradle 同步。
- 通过依次点击 Run > Run 'app',确保播放器应用在 Android 实体设备或 Android 虚拟设备上编译和运行。视频流加载需要一些时间才能正常播放。
检查示例视频播放器
示例视频播放器尚无任何 IMA SDK 集成代码。该示例应用包含两个主要部分:
samplevideoplayer/SampleVideoPlayer.java
- 基于 ExoPlayer 的简单 HLS 播放器,可用作 IMA DAI 集成的基础。videoplayerapp/MyActivity.java
- 此 Activity 会创建视频播放器,并向其传递Context
和SimpleExoPlayerView
。
将 IMA Android SDK 添加到播放器应用中
此外,您还必须添加对 IMA SDK 的引用。在 Android Studio 中,将以下内容添加到位于 app/build.gradle
的应用级 build.gradle
文件中:
repositories { google() jcenter() } dependencies { implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'androidx.browser:browser:1.3.0' implementation 'com.google.android.exoplayer:exoplayer:2.18.1' implementation 'com.google.ads.interactivemedia.v3:interactivemedia:3.29.0' }
集成 IMA SDK
-
在
videoplayerapp
软件包中(在app/java/com.google.ads.interactivemedia.v3.samples/videoplayerapp/
中)创建一个名为SampleAdsWrapper
的新类,以封装现有的SampleVideoPlayer
,并添加实现 IMA 动态广告插播 (DAI) 的逻辑。为此,您必须先创建一个AdsLoader
,用于向广告服务器请求广告。videoplayerapp/SampleAds 封装容器.java
package com.google.ads.interactivemedia.v3.samples.videoplayerapp; import android.annotation.TargetApi; import android.content.Context; import android.os.Build; import android.view.ViewGroup; import android.webkit.WebView; import com.google.ads.interactivemedia.v3.api.AdErrorEvent; import com.google.ads.interactivemedia.v3.api.AdEvent; import com.google.ads.interactivemedia.v3.api.AdsLoader; import com.google.ads.interactivemedia.v3.api.AdsManagerLoadedEvent; import com.google.ads.interactivemedia.v3.api.CuePoint; import com.google.ads.interactivemedia.v3.api.ImaSdkFactory; import com.google.ads.interactivemedia.v3.api.ImaSdkSettings; import com.google.ads.interactivemedia.v3.api.StreamDisplayContainer; import com.google.ads.interactivemedia.v3.api.StreamManager; import com.google.ads.interactivemedia.v3.api.StreamRequest; import com.google.ads.interactivemedia.v3.api.player.VideoProgressUpdate; import com.google.ads.interactivemedia.v3.api.player.VideoStreamPlayer; import com.google.ads.interactivemedia.v3.samples.samplevideoplayer.SampleVideoPlayer; import java.util.ArrayList; import java.util.HashMap; import java.util.List; public class SampleAdsWrapper implements AdEvent.AdEventListener, AdErrorEvent.AdErrorListener, AdsLoader.AdsLoadedListener { // Live stream asset key. private static final String TEST_ASSET_KEY = "sN_IYUG8STe1ZzhIIE_ksA"; // VOD content source and video IDs. private static final String TEST_CONTENT_SOURCE_ID = "2528370"; private static final String TEST_VIDEO_ID = "tears-of-steel"; private static final String PLAYER_TYPE = "DAISamplePlayer"; /** * Log interface, so you can output the log commands to the UI or similar. */ public interface Logger { void log(String logMessage); } private ImaSdkFactory sdkFactory; private AdsLoader adsLoader; private StreamDisplayContainer displayContainer; private StreamManager streamManager; private List<VideoStreamPlayer.VideoStreamPlayerCallback> playerCallbacks; private SampleVideoPlayer videoPlayer; private Context context; private ViewGroup adUiContainer; private String fallbackUrl; private Logger logger; public SampleAdsWrapper(Context context, SampleVideoPlayer videoPlayer, ViewGroup adUiContainer) { this.videoPlayer = videoPlayer; this.context = context; this.adUiContainer = adUiContainer; sdkFactory = ImaSdkFactory.getInstance(); playerCallbacks = new ArrayList<>(); createAdsLoader(); displayContainer = sdkFactory.createStreamDisplayContainer( this.adUiContainer, videoStreamPlayer ); } private void createAdsLoader() { ImaSdkSettings settings = new ImaSdkSettings(); adsLoader = sdkFactory.createAdsLoader(context); } public void requestAndPlayAds() { adsLoader.addAdErrorListener(this); adsLoader.addAdsLoadedListener(this); adsLoader.requestStream(buildStreamRequest()); } }
-
向
AdsLoader
添加buildStreamRequest()
方法,以便它可以请求包含广告的流。此直播可以是包含广告的直播(默认设置),也可以是随广告一起播放预先录制的内容的视频点播 (VOD) 直播。如需启用 VOD 视频流,请注释掉相应的直播请求,并取消对 VOD 视频流请求的注释。videoplayerapp/SampleAds 封装容器.java
private StreamRequest buildStreamRequest() { VideoStreamPlayer videoStreamPlayer = createVideoStreamPlayer(); videoPlayer.setSampleVideoPlayerCallback( new SampleVideoPlayer.SampleVideoPlayerCallback() { @Override public void onUserTextReceived(String userText) { for (VideoStreamPlayer.VideoStreamPlayerCallback callback : playerCallbacks) { callback.onUserTextReceived(userText); } } @Override public void onSeek(int windowIndex, long positionMs) { // See if you would seek past an ad, and if so, jump back to it. long newSeekPositionMs = positionMs; if (streamManager != null) { CuePoint prevCuePoint = streamManager.getPreviousCuePointForStreamTime(positionMs / 1000); if (prevCuePoint != null && !prevCuePoint.isPlayed()) { newSeekPositionMs = (long) (prevCuePoint.getStartTime() * 1000); } } videoPlayer.seekTo(windowIndex, newSeekPositionMs); } @Override public void onContentComplete() { for (VideoStreamPlayer.VideoStreamPlayerCallback callback : playerCallbacks) { callback.onContentComplete(); } } @Override public void onPause() { for (VideoStreamPlayer.VideoStreamPlayerCallback callback : playerCallbacks) { callback.onPause(); } } @Override public void onResume() { for (VideoStreamPlayer.VideoStreamPlayerCallback callback : playerCallbacks) { callback.onResume(); } } @Override public void onVolumeChanged(int percentage) { for (VideoStreamPlayer.VideoStreamPlayerCallback callback : playerCallbacks) { callback.onVolumeChanged(percentage); } } }); // Live stream request. StreamRequest request = sdkFactory.createLiveStreamRequest( TEST_ASSET_KEY, null, displayContainer); // VOD request. Comment the createLiveStreamRequest() line above and uncomment this // createVodStreamRequest() below to switch from a live stream to a VOD stream. // StreamRequest request = sdkFactory.createVodStreamRequest(TEST_CONTENT_SOURCE_ID, // TEST_VIDEO_ID, null, displayContainer); return request; }
-
您还需要一个
VideoStreamPlayer
才能播放该流,因此请添加createVideoStreamPlayer()
方法,以创建一个实现VideoStreamPlayer
的匿名类。videoplayerapp/SampleAds 封装容器.java
private VideoStreamPlayer createVideoStreamPlayer() { VideoStreamPlayer player = new VideoStreamPlayer() { @Override public void loadUrl(String url, List<HashMap<String, String>> subtitles) { videoPlayer.setStreamUrl(url); videoPlayer.play(); } @Override public void addCallback( VideoStreamPlayerCallback videoStreamPlayerCallback) { playerCallbacks.add(videoStreamPlayerCallback); } @Override public void removeCallback( VideoStreamPlayerCallback videoStreamPlayerCallback) { playerCallbacks.remove(videoStreamPlayerCallback); } @Override public void onAdBreakStarted() { // Disable player controls. videoPlayer.enableControls(false); log("Ad Break Started\n"); } @Override public void onAdBreakEnded() { // Re-enable player controls. videoPlayer.enableControls(true); log("Ad Break Ended\n"); } @Override public VideoProgressUpdate getContentProgress() { return new VideoProgressUpdate(videoPlayer.getCurrentPosition(), videoPlayer.getDuration()); } }; return player; }
-
实现所需的监听器并添加对错误处理的支持。
重要提示:请注意AdErrorListener
实现,因为它会在广告无法播放时调用后备网址。由于内容和广告位于同一视频流中,因此您必须准备好在 DAI 视频流出错时调用后备视频流。videoplayerapp/SampleAds 封装容器.java
/** AdErrorListener implementation **/ @Override public void onAdError(AdErrorEvent event) { // play fallback URL. videoPlayer.setStreamUrl(fallbackUrl); videoPlayer.enableControls(true); videoPlayer.play(); } /** AdEventListener implementation **/ @Override public void onAdEvent(AdEvent event) { switch (event.getType()) { case AD_PROGRESS: // Do nothing or else log are filled by these messages. break; default: log(String.format("Event: %s\n", event.getType())); break; } } /** AdsLoadedListener implementation **/ @Override public void onAdsManagerLoaded(AdsManagerLoadedEvent event) { streamManager = event.getStreamManager(); streamManager.addAdErrorListener(this); streamManager.addAdEventListener(this); streamManager.init(); } /** Sets fallback URL in case ads stream fails. **/ void setFallbackUrl(String url) { fallbackUrl = url; }
-
添加用于日志记录的代码。
videoplayerapp/SampleAds 封装容器.java
/** Sets logger for displaying events to screen. Optional. **/ void setLogger(Logger logger) { this.logger = logger; } private void log(String message) { if (logger != null) { logger.log(message); } }
- 修改
videoplayerapp
中的MyActivity
以进行实例化并调用SampleAdsWrapper
。videoplayerapp/MyActivity.java
… import android.view.ViewGroup; import android.widget.ScrollView; … public class MyActivity extends AppCompatActivity { … @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_my); View rootView = findViewById(R.id.videoLayout); videoPlayer = new SampleVideoPlayer(rootView.getContext(), (SimpleExoPlayerView) rootView.findViewById(R.id.playerView)); videoPlayer.enableControls(false); final SampleAdsWrapper sampleAdsWrapper = new SampleAdsWrapper(this, videoPlayer, (ViewGroup) rootView.findViewById(R.id.adUiContainer)); sampleAdsWrapper.setFallbackUrl(DEFAULT_STREAM_URL); final ScrollView scrollView = (ScrollView) findViewById(R.id.logScroll); final TextView textView = (TextView) findViewById(R.id.logText); sampleAdsWrapper.setLogger(new SampleAdsWrapper.Logger() { @Override public void log(String logMessage) { Log.i(APP_LOG_TAG, logMessage); if (textView != null) { textView.append(logMessage); } if (scrollView != null) { scrollView.post(new Runnable() { @Override public void run() { scrollView.fullScroll(View.FOCUS_DOWN); } }); } } }); playButton = (ImageButton) rootView.findViewById(R.id.playButton); // Set up play button listener to play video then hide play button. playButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { sampleAdsWrapper.requestAndPlayAds(); playButton.setVisibility(View.GONE); } }); } … }
- 修改 Activity 的布局文件
activity_my.xml
,以添加用于日志记录的界面元素。res/layout/activity_my.xml
… <TextView android:id="@+id/playerDescription" android:text="@string/video_description" android:textAlignment="center" android:gravity="center_horizontal" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="0.1" android:textSize="@dimen/font_size" /> <!-- UI element for viewing SDK event log --> <ScrollView android:id="@+id/logScroll" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="0.5" android:padding="5dp" android:background="#DDDDDD"> <TextView android:id="@+id/logText" android:layout_width="match_parent" android:layout_height="wrap_content"> </TextView> </ScrollView> …
恭喜!您正在 Android 应用中请求并展示视频广告。如需微调您的实现,请参阅书签和 Snapback 指南以及 API 文档。
问题排查
如果您在播放视频广告时遇到问题,请尝试下载已完成的基本示例。如果它在 BasicExample 中正常运行,则表示该应用的 IMA 集成代码可能存在问题。 请查看本指南和 API 文档,以检测任何差异。
仍然有问题?请在 IMA SDK 论坛上留言。