ExoPlayer IMA 扩展程序使用入门

ExoPlayer 是一款适用于 Android 的应用级媒体播放器。本指南介绍了如何使用封装了 IMA DAI SDK 的 ExoPlayer IMA 扩展程序请求和播放包含广告和内容的媒体流。

以下是该扩展程序的一些优势:

  • 简化了将 IMA 与功能集成所需的代码。
  • 缩短更新到新版 IMA 所需的开发时间。

ExoPlayer IMA 扩展支持 HLS 和 DASH 流式传输协议。下面是摘要:

ExoPlayer-IMA 扩展流支持
直播 VOD 串流
HLS 对勾标记 对勾标记
DASH 对勾标记 对勾标记

ExoPlayer-IMA 版本 1.1.0 及更高版本支持 DASH 直播。

本指南基于 ExoPlayer 指南,介绍了如何创建完整应用并集成扩展程序。如需查看包含完整示例应用的示例,请参阅 GitHub 上的 ExoPlayerExample

前提条件

创建一个新的 Android Studio 项目

如需创建 Android Studio 项目,请完成以下步骤:

  • 启动 Android Studio。
  • 选择 Start a new Android Studio project
  • Choose your project 页面中,选择 No Activity 模板。
  • 点击下一步
  • Configure your project 页面中,为项目命名,然后选择 Java 作为语言。

  • 点击完成

将 ExoPlayer IMA 扩展程序添加到您的项目中

将扩展程序的导入添加到应用级 build.gradle 文件的 dependencies 部分。

配置您的应用并启用 multidex。由于扩展程序的大小,因此必须这样做,对于将 minSdkVersion 设置为 Android 4.4W(API 级别 20) 或更低版本的应用,更是如此。

示例如下:

app/build.gradle

android {

  ...

  defaultConfig {
      applicationId "com.google.ads.interactivemedia.v3.samples.videoplayerapp"
      minSdkVersion 21
      targetSdkVersion 34
      multiDexEnabled true
      versionCode 1
      versionName "1.0"
  }

    ...
}
dependencies {
    implementation 'androidx.multidex:multidex:2.0.1'
    implementation 'androidx.media3:media3-ui:1.1.1'
    implementation 'androidx.media3:media3-exoplayer:1.1.1'
    implementation 'androidx.media3:media3-exoplayer-hls:1.1.1'
    implementation 'androidx.media3:media3-exoplayer-dash:1.1.1'

    // Adding the ExoPlayer IMA extension for ads will also include the IMA
    // SDK as a dependency.
    implementation 'androidx.media3:media3-exoplayer-ima:1.1.1'
}

添加 IMA DAI SDK 请求广告所需的用户权限:

app/src/main/AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.project name">

    <!-- Required permissions for the IMA DAI SDK -->
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

    ...

</manifest>

添加 intent 声明

如果您的应用以 Android 11(API 级别 30)或更高版本为目标平台,当前和近期版本的 IMA DAI SDK 要求明确声明打开网页链接的 intent。将以下代码段添加到应用的清单文件中,以启用广告点击(用户点击了解详情按钮)。

  <?xml version="1.0" encoding="utf-8"?>
  <manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.example.project name">

      ...

    </application>

    <queries>
      <intent>
          <action android:name="android.intent.action.VIEW" />
          <data android:scheme="https" />
      </intent>
      <intent>
          <action android:name="android.intent.action.VIEW" />
          <data android:scheme="http" />
      </intent>
    </queries>
  </manifest>

设置 ExoPlayer 的界面

创建要供 ExoPlayer 使用的 PlayerView 对象。

androidx.constraintlayout.widget.ConstraintLayout 更改为 LinearLayout,这是 ExoPlayer IMA 扩展程序的推荐值。

示例如下:

app/src/main/res/layout/activity_my.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:background="@android:color/black"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MyActivity"
    tools:ignore="MergeRootFrame">

    <androidx.media3.ui.PlayerView
        android:id="@+id/player_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

添加直播参数

如需获取用于测试项目的示例串流资产,请参阅 IMA 示例串流页面。如需了解如何设置自己的数据流,请参阅 DAI 中的 Ad Manager 部分

此步骤演示了如何设置直播,但 ExoPlayer IMA 扩展程序也支持 DAI VOD 直播。请参阅视频点播 (VOD) 串流的步骤,了解您的应用需要进行哪些更改才能处理 VOD 串流。

导入 ExoPlayer IMA 扩展程序

添加 ExoPlayer 扩展程序的 import 语句。

将以下私有变量添加到 MyActivity.java

添加 Big Buck Bunny (Live) HLS 直播的素材资源键,以便使用此直播进行测试。您可以在 IMA 的示例串流页面上测试更多串流。

创建一个 KEY_ADS_LOADER_STATE 常量以保存和检索 AdsLoader 状态。

示例如下:

app/src/main/java/com/example/project name/MyActivity.java


import static androidx.media3.common.C.CONTENT_TYPE_HLS;

import android.app.Activity;
import android.net.Uri;
import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.annotation.OptIn;
import androidx.media3.common.MediaItem;
import androidx.media3.common.util.Util;
import androidx.media3.datasource.DataSource;
import androidx.media3.datasource.DefaultDataSource;
import androidx.media3.exoplayer.ExoPlayer;
import androidx.media3.exoplayer.ima.ImaServerSideAdInsertionMediaSource;
import androidx.media3.exoplayer.ima.ImaServerSideAdInsertionUriBuilder;
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory;
import androidx.media3.exoplayer.util.EventLogger;
import androidx.media3.ui.PlayerView;
import androidx.multidex.MultiDex;

...

public class MyActivity extends Activity {

  private static final String KEY_ADS_LOADER_STATE = "ads_loader_state";
  private static final String SAMPLE_ASSET_KEY = "c-rArva4ShKVIAkNfy6HUQ";

  private PlayerView playerView;
  private ExoPlayer player;
  private ImaServerSideAdInsertionMediaSource.AdsLoader adsLoader;
  private ImaServerSideAdInsertionMediaSource.AdsLoader.State adsLoaderState;

}

创建 adsLoader 实例

覆盖 onCreate 方法以查找 PlayerView 并检查是否有已保存的 AdsLoader.State,该方法可在启动 adsLoader 对象时使用。

此外,如果应用的方法数和 minSdkVersion 需要,请启用 MultiDex(如第 2 步中所述)。

示例如下:

app/src/main/java/com/example/project name/MyActivity.java

...

public class MyActivity extends Activity {

  private static final String KEY_ADS_LOADER_STATE = "ads_loader_state";
  private static final String SAMPLE_ASSET_KEY = "c-rArva4ShKVIAkNfy6HUQ";

  private PlayerView playerView;
  private ExoPlayer player;
  private ImaServerSideAdInsertionMediaSource.AdsLoader adsLoader;
  private ImaServerSideAdInsertionMediaSource.AdsLoader.State adsLoaderState;

  @Override
  protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_my);
    MultiDex.install(this);

    playerView = findViewById(R.id.player_view);

    // Checks if there is a saved AdsLoader state to be used later when
    // initiating the AdsLoader.
    if (savedInstanceState != null) {
      Bundle adsLoaderStateBundle = savedInstanceState.getBundle(KEY_ADS_LOADER_STATE);
      if (adsLoaderStateBundle != null) {
        adsLoaderState =
            ImaServerSideAdInsertionMediaSource.AdsLoader.State.fromBundle(
                adsLoaderStateBundle);
      }
    }
  }

}

添加用于初始化播放器的方法

添加一个用于初始化播放器的方法,并执行以下操作:

  • 创建一个 AdsLoader 实例。
  • 创建 ExoPlayer
  • 使用直播的资产键创建 MediaItem
  • MediaItem 设置为您的播放器。

示例如下:

app/src/main/java/com/example/project name/MyActivity.java

public class MyActivity extends Activity {

  ...

  
  // Create a server side ad insertion (SSAI) AdsLoader.
  private ImaServerSideAdInsertionMediaSource.AdsLoader createAdsLoader() {
    ImaServerSideAdInsertionMediaSource.AdsLoader.Builder adsLoaderBuilder =
        new ImaServerSideAdInsertionMediaSource.AdsLoader.Builder(this, playerView);

    // Attempt to set the AdsLoader state if available from a previous session.
    if (adsLoaderState != null) {
      adsLoaderBuilder.setAdsLoaderState(adsLoaderState);
    }

    return adsLoaderBuilder.build();
  }

  private void initializePlayer() {
    adsLoader = createAdsLoader();

    // Set up the factory for media sources, passing the ads loader.
    DataSource.Factory dataSourceFactory = new DefaultDataSource.Factory(this);
    DefaultMediaSourceFactory mediaSourceFactory = new DefaultMediaSourceFactory(dataSourceFactory);

    // MediaSource.Factory to create the ad sources for the current player.
    ImaServerSideAdInsertionMediaSource.Factory adsMediaSourceFactory =
        new ImaServerSideAdInsertionMediaSource.Factory(adsLoader, mediaSourceFactory);

    // 'mediaSourceFactory' is an ExoPlayer component for the DefaultMediaSourceFactory.
    // 'adsMediaSourceFactory' is an ExoPlayer component for a MediaSource factory for IMA server
    // side inserted ad streams.
    mediaSourceFactory.setServerSideAdInsertionMediaSourceFactory(adsMediaSourceFactory);

    // Create an ExoPlayer and set it as the player for content and ads.
    player = new ExoPlayer.Builder(this).setMediaSourceFactory(mediaSourceFactory).build();
    playerView.setPlayer(player);
    adsLoader.setPlayer(player);

    // Build an IMA SSAI media item to prepare the player with.
    Uri ssaiLiveUri =
        new ImaServerSideAdInsertionUriBuilder()
            .setAssetKey(SAMPLE_ASSET_KEY)
            .setFormat(CONTENT_TYPE_HLS) // Use CONTENT_TYPE_DASH for dash streams.
            .build();

    // Create the MediaItem to play, specifying the stream URI.
    MediaItem ssaiMediaItem = MediaItem.fromUri(ssaiLiveUri);

    // Prepare the content and ad to be played with the ExoPlayer.
    player.setMediaItem(ssaiMediaItem);
    player.prepare();

    // Set PlayWhenReady. If true, content and ads will autoplay.
    player.setPlayWhenReady(false);
  }
}

添加用于释放播放器的方法

添加一个方法以按以下顺序释放玩家:

  • 将播放器引用设为 null,并释放播放器的资源。
  • 释放 adsLoader 的状态。

app/src/main/java/com/example/project name/MyActivity.java

public class MyActivity extends Activity {

  ...

  private void releasePlayer() {
    // Set the player references to null and release the player's resources.
    playerView.setPlayer(null);
    player.release();
    player = null;

    // Release the adsLoader state so that it can be initiated again.
    adsLoaderState = adsLoader.release();
  }

处理播放器事件

最后,为 activity 的生命周期事件创建回调,以处理流式播放。

如需支持 Android SDK 24 及更高版本,请执行以下操作:

如需支持低于 24 的 Android SDK 版本,请执行以下操作: - onResume() - onPause()

onStart()onResume() 映射到 playerView.onResume()onStop()onPause() 映射到 playerView.onPause()

此步骤还使用 onSaveInstanceState() 事件尝试保存 adsLoaderState

app/src/main/java/com/example/project name/MyActivity.java

public class MyActivity extends Activity {

  ...

  @Override
  public void onStart() {
    super.onStart();
    if (Util.SDK_INT > 23) {
      initializePlayer();
      if (playerView != null) {
        playerView.onResume();
      }
    }
  }

  @Override
  public void onResume() {
    super.onResume();
    if (Util.SDK_INT <= 23 || player == null) {
      initializePlayer();
      if (playerView != null) {
        playerView.onResume();
      }
    }
  }

  @Override
  public void onPause() {
    super.onPause();
    if (Util.SDK_INT <= 23) {
      if (playerView != null) {
        playerView.onPause();
      }
      releasePlayer();
    }
  }

  @Override
  public void onStop() {
    super.onStop();
    if (Util.SDK_INT > 23) {
      if (playerView != null) {
        playerView.onPause();
      }
      releasePlayer();
    }
  }

  @Override
  public void onSaveInstanceState(Bundle outState) {
    // Attempts to save the AdsLoader state to handle app backgrounding.
    if (adsLoaderState != null) {
      outState.putBundle(KEY_ADS_LOADER_STATE, adsLoaderState.toBundle());
    }
  }

  ...

}

VOD 视频流设置(可选)

如果您的应用需要播放带广告的 VOD 内容,您需要执行以下操作:

  1. 为 VOD 测试串流添加了 CMS IDVideo ID
  2. 使用 ImaServerSideAdInsertionUriBuilder() 创建 SSAI VOD URI。
  3. 将此新 URI 用作播放器的媒体内容。

app/src/main/java/com/example/project name/MyActivity.java

public class MyActivity extends Activity {

  private static final String KEY_ADS_LOADER_STATE = "ads_loader_state";
  private static final String SAMPLE_ASSET_KEY = "c-rArva4ShKVIAkNfy6HUQ";
  private static final String SAMPLE_CMS_ID = "2548831";
  private static final String SAMPLE_VIDEO_ID = "tears-of-steel";

  ...

  private void initializePlayer() {

     ...

    Uri ssaiVodUri =
        new ImaServerSideAdInsertionUriBuilder()
            .setContentSourceId(SAMPLE_CMS_ID)
            .setVideoId(SAMPLE_VIDEO_ID)
            .setFormat(CONTENT_TYPE_HLS)
            .build();

    // Create the MediaItem to play, specifying the stream URI.
    MediaItem ssaiMediaItem = MediaItem.fromUri(ssaiVodUri);

    // Prepare the content and ad to be played with the ExoPlayer.
    player.setMediaItem(ssaiMediaItem);
    player.prepare();

    // Set PlayWhenReady. If true, content and ads will autoplay.
    player.setPlayWhenReady(false);
  }

大功告成!现在,您可以使用 ExoPlayer IMA 扩展程序请求和播放媒体流式传输内容。如需查看完整代码,请参阅 GitHub 上的 Android DAI 示例