使用 IMA SDK for Android 自定义广告播放

若要将 IMA SDK for Android 集成到您的应用中,最快速、最直接的方法就是让 SDK 处理所有的广告播放逻辑,而让您的应用专注于播放内容视频。此方法称为“SDK 自有广告播放”,是使用入门中的默认选项。

不过,如果您还想在视频播放器中播放广告,SDK 就提供了一个接口。我们将这种方法称为“自定义广告播放”,本指南的其余部分将介绍其实现方法。

前提条件

  • 基本的 IMA 集成。

如果您目前没有基本的 IMA 集成,我们建议您先查看 GitHub 上的高级示例。此示例已经实现了自定义广告播放。本指南的其余部分将介绍使用 IMA 广告播放自定义广告所需的功能。

VideoAdPlayer 界面

自定义广告播放要求您的应用实现 VideoAdPlayer 接口。SDK 使用此接口通知您的应用播放广告视频。您的应用还使用此接口将主要视频广告事件告知 SDK。请按照以下步骤实现该接口。

创建 VideoAdPlayer

第一步是在 requestAds() 中创建匿名 VideoAdPlayer 类:

private VideoAdPlayer videoAdPlayer;
...

private void requestAds(String adTagUrl) {
    videoAdPlayer = new VideoAdPlayer() {
    };
}

添加视频方法

接下来,添加指示视频播放器播放、加载、停止和暂停广告视频的方法。我们还在此处添加了释放播放器并获取音量的方法:

videoAdPlayer = new VideoAdPlayer() {
        @Override
        public void playAd() {
            if (mIsAdDisplayed) {
                videoPlayer.resume();
            } else {
                isAdDisplayed = true;
                videoPlayer.play();
            }
        }

        @Override
        public void loadAd(String url) {
            isAdDisplayed = true;
            videoPlayer.setVideoPath(url);
        }
        @Override
        public void stopAd() {
            videoPlayer.stopPlayback();
        }
        @Override
        public void pauseAd() {
            videoPlayer.pause();
        }

        @Override
        public void release() {
            // any clean up that needs to be done
        }

        @Override
        public int getVolume() {
            return videoPlayer.getVolume();
        }
};

这些方法是视频播放器自身的类似方法的精简封装容器。请注意,这些方法会设置一个内部变量,用于跟踪广告是否展示。在自定义广告播放过程中,视频播放器会同时播放内容视频广告和视频广告,因此您需要跟踪当前展示的视频广告。

广告播放进度

VideoAdPlayer 接口会实现另一个接口,即 AdProgressProvider,因此您还必须实现该接口。它只有一种方法(即 getAdProgress()),SDK 使用该方法获取广告的播放信息。将其添加到您的匿名 VideoAdPlayer 类中(在其他方法下方):

VideoAdPlayer videoAdPlayer = new VideoAdPlayer() {
        ...
        @Override
        public VideoProgressUpdate getAdProgress() {
            if (!isAdDisplayed || videoPlayer.getDuration() <= 0) {
                return VideoProgressUpdate.VIDEO_TIME_NOT_READY;
            }
            return new VideoProgressUpdate(videoPlayer.getCurrentPosition(),
                    videoPlayer.getDuration());
        }
};

getAdProgress() 会返回 VideoProgressUpdate 类型,该类型必须包含视频的当前位置和时长。如果播放器未播放广告,或者无法播放时长,则让其返回 VideoProgressUpdate.VIDEO_TIME_NOT_READY,如示例中所示。

管理视频回调

自定义广告播放要求您的应用向 SDK 通知重大视频事件。从 SDK 的视图中,这些是由 VideoAdPlayer.VideoAdPlayerCallback 接口描述的回调。在调用回调方法本身之前,您需要能够应 SDK 的请求添加和移除回调。这是在 VideoAdPlayer 内使用 addCallback()removeCallback() 完成的:

private List<VideoAdPlayerCallback> adCallbacks = new ArrayList<>(1);

VideoAdPlayer videoAdPlayer = new VideoAdPlayer() {
        ...
        @Override
        public void addCallback(VideoAdPlayerCallback videoAdPlayerCallback) {
            adCallbacks.add(videoAdPlayerCallback);
        }

        @Override
        public void removeCallback(VideoAdPlayerCallback videoAdPlayerCallback) {
            adCallbacks.remove(videoAdPlayerCallback);
        }
};

此实现对要对其调用 List<>.add()remove() 方法的回调使用 List<>

调用回调

现在,SDK 已经能够指示应用添加和移除回调,接下来可以定义在哪些位置调用回调。当发生重大视频事件(例如播放、暂停或恢复视频,或者视频播放完毕或遇到错误)时,您的应用需要调用这些回调。

为此,请展开 SampleVideoPlayer 以添加从 VideoFragment 添加的这些视频事件的监听器。之所以在 SampleVideoPlayer 中创建一个单独的监听器来调用这些广告回调,是因为 SampleVideoPlayer 并不了解广告的任何信息,因此您必须将其视频事件转发给可以处理广告的内容。

public interface OnVideoEventsListener {
    void onPlay();
    void onResume();
    void onPause();
    void onError();
}

private final List<OnVideoEventsListener> onVideoEventsListeners = new ArrayList<>(1);

public void addVideoEventsListener(OnVideoEventsListener listener) {
    onVideoEventsListeners.add(listener);
}

启动、暂停和继续

创建一个新的枚举以跟踪播放状态,并为 SampleVideoPlayer 中的 start()pause() 方法添加新的替换项:

private enum PlaybackState {
    STOPPED, PAUSED, PLAYING
}

private PlaybackState playbackState = PlaybackState.STOPPED;

@Override
public void start() {
    super.start();
    switch (playbackState) {
        case STOPPED:
            for (OnVideoEventsListener listener : onVideoEventsListeners) {
                listener.onPlay();
            }
            break;
        case PAUSED:
            for (OnVideoEventsListener listener : onVideoEventsListeners) {
                listener.onResume();
            }
            break;
        default:
            // Already playing; do nothing.
            break;
    }
    playbackState = PlaybackState.PLAYING;
}

@Override
public void pause() {
    super.pause();
    playbackState = PlaybackState.PAUSED;
    for (OnVideoEventsListener listener : onVideoEventsListeners) {
        listener.onPause();
    }
}

处理错误

替换您在 init() 中设置的视频播放器匿名错误监听器:

@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
    playbackState = PlaybackState.STOPPED;
    for (OnVideoEventsListener listener : onVideoEventsListeners) {
        listener.onError();
    }

    // Returning true signals to MediaPlayer that the error was handled.
    // This  prevents the completion handler from being called.
    return true;
}

实现监听器

返回 VideoFragment,将匿名 OnVideoEventsListener 添加到您的 SampleVideoPlayer 实例:

mVideoPlayer.addVideoEventsListener(new OnVideoEventsListener() {
    @Override
    public void onPlay() {
        if (isAdDisplayed) {
            for (VideoAdPlayerCallback callback : adCallbacks) {
                callback.onPlay();
            }
        }
    }

    @Override
    public void onResume() {
        if (isAdDisplayed) {
            for (VideoAdPlayerCallback callback : adCallbacks) {
                callback.onResume();
            }
        }
    }

    @Override
    public void onPause() {
        if (isAdDisplayed) {
            for (VideoAdPlayerCallback callback : adCallbacks) {
                callback.onPause();
            }
        }
    }

    @Override
    public void onError() {
        if (isAdDisplayed) {
            for (VideoAdPlayerCallback callback : adCallbacks) {
                callback.onError();
            }
        }
    }
});

更改 OnVideoCompletedListeneronVideoCompleted() 方法,以处理广告视频完整播放的情况:

public void onVideoCompleted() {
    // Handle completed event for playing post-rolls.
    if (isAdDisplayed) {
        for (VideoAdPlayerCallback callback : adCallbacks) {
            callback.onEnded();
        }
    } else {
        if (adsLoader != null) {
            adsLoader.contentComplete();
    }
}

在内容和广告之间切换

此示例使用视频播放器的同一实例来播放内容和广告,因此您需要添加一些逻辑,以在播放器中的内容和广告之间切换。然后,您可以重新加载并跳转至内容视频以返回到广告开始播放的位置。为此,请添加两个函数:

private int savedContentPosition = 0;

private void pauseContent() {
    savedContentPosition = videoPlayer.getCurrentPosition();
    videoPlayer.stopPlayback();
    isAdDisplayed = true;
}

private void resumeContent() {
    videoPlayer.setVideoPath(getString(R.string.content_url));
    videoPlayer.seekTo(mSavedContentPosition);
    videoPlayer.play();
    isAdDisplayed = false;
}

当在 VideoFragment.onAdEvent() 中收到 CONTENT_PAUSE_REQUESTEDCONTENT_RESUME_REQUESTED 事件时,系统会分别调用这两个事件:

case CONTENT_PAUSE_REQUESTED:
    pauseContent();
    break;
case CONTENT_RESUME_REQUESTED:
    resumeContent();
    break;

启用自定义广告播放

最后一步是告知 SDK 您正在使用自定义广告播放。 通过将 VideoAdPlayer 传递给 AdDisplayContainer 来完成此操作:

adDisplayContainer.setPlayer(videoAdPlayer);

您需要将播放器传递给 setPlayer()。否则,SDK 会使用 SDK 拥有的播放机制。

大功告成。以上是向您的 IMA 实现中添加自定义广告播放所需的所有步骤。如果您遇到问题,可以将您自己的实现与 GitHub 上的高级示例进行比较。