DAI용 IMA SDK 설정

IMA SDK를 사용하면 웹사이트와 앱에 멀티미디어 광고를 쉽게 통합할 수 있습니다. IMA SDK는 모든 VAST 호환 광고 서버에서 광고를 요청하고 앱에서 광고 재생을 관리할 수 있습니다. IMA DAI SDK를 사용하면 앱에서 광고 및 콘텐츠 동영상(VOD 또는 라이브 콘텐츠)에 대한 스트림을 요청합니다. 그러면 SDK가 결합된 동영상 스트림을 반환하므로 앱 내에서 광고 동영상과 콘텐츠 동영상 간에 전환을 관리할 필요가 없습니다.

관심 있는 DAI 솔루션을 선택합니다

종합 서비스 DAI

이 가이드에서는 IMA DAI SDK를 간단한 동영상 플레이어 앱에 통합하는 방법을 설명합니다. 완성된 샘플 통합을 보거나 따라 하려면 GitHub에서 BasicExample을 다운로드하세요.

IMA DAI 개요

IMA DAI를 구현하려면 이 가이드에 설명된 4가지 주요 SDK 구성요소가 필요합니다.

  • StreamDisplayContainer: 동영상 재생 요소 위에 있고 광고 UI 요소를 포함하는 컨테이너 객체입니다.
  • AdsLoader: 스트림을 요청하고 스트림 요청 응답 객체에 의해 트리거된 이벤트를 처리하는 객체입니다. 애플리케이션 전체 기간 동안 재사용할 수 있는 광고 로더는 하나만 인스턴스화해야 합니다.
  • StreamRequest: 스트림 요청을 정의하는 객체입니다. 스트림 요청은 VOD 또는 라이브 스트림일 수 있습니다. 라이브 스트림 요청은 애셋 키를 지정하고 VOD 요청은 CMS ID와 동영상 ID를 지정합니다. 두 요청 유형 모두 원하는 경우 지정된 스트림에 액세스하는 데 필요한 API 키와 IMA SDK가 Google Ad Manager 설정에 지정된 대로 광고 식별자를 처리하기 위한 Google Ad Manager 네트워크 코드를 포함할 수 있습니다.
  • StreamManager: 동적 광고 삽입 스트림과 DAI 백엔드와의 상호작용을 처리하는 객체입니다. 스트림 관리자는 추적 핑도 처리하고 스트림 및 광고 이벤트를 게시자에게 전달합니다.

기본 요건

샘플 동영상 플레이어 앱 다운로드 및 실행

샘플 앱은 HLS 동영상을 재생하는 작동하는 동영상 플레이어를 제공합니다. 이를 IMA DAI SDK의 DAI 기능을 통합하기 위한 시작점으로 사용하세요.

  1. 샘플 동영상 플레이어 앱을 다운로드하고 압축을 풉니다.

  2. Android 스튜디오를 시작하고 Open an existing Android Studio project(기존 Android 스튜디오 프로젝트 열기)를 선택합니다. 또는 Android 스튜디오가 이미 실행 중인 경우 File > New > Import Project(파일 > 새 파일 > 프로젝트 가져오기)를 선택합니다. 그런 다음 SampleVideoPlayer/build.gradle를 선택합니다.

  3. Tools > Android > Sync Project with Gradle Files를 선택하여 Gradle 동기화를 실행합니다.

  4. Run > Run 'app'을 사용하여 플레이어 앱이 실제 Android 기기 또는 Android 가상 기기에서 컴파일되고 실행되는지 확인합니다. 동영상 스트림이 재생되기 전에 로드되는 데 잠시 시간이 걸리는 것은 정상입니다.

샘플 동영상 플레이어 검사

샘플 동영상 플레이어에는 아직 IMA DAI SDK 통합 코드가 포함되어 있지 않습니다. 샘플 앱은 두 가지 주요 부분으로 구성됩니다.

  1. samplevideoplayer/SampleVideoPlayer.java: IMA DAI 통합의 기반이 되는 ExoPlayer 기반 HLS 플레이어입니다.

  2. videoplayerapp/MyActivity.java: 이 활동은 동영상 플레이어를 만들고 Contextmedia3.ui.PlayerView를 전달합니다.

플레이어 앱에 IMA DAI SDK 추가

IMA DAI SDK에 대한 참조도 포함해야 합니다. Android 스튜디오에서 app/build.gradle에 있는 애플리케이션 수준 build.gradle 파일에 다음을 추가합니다.

repositories {
    google()
    mavenCentral()
}

dependencies {
    def media3_version = "1.5.1"
    implementation(platform("org.jetbrains.kotlin:kotlin-bom:1.8.0"))
    implementation 'androidx.appcompat:appcompat:1.7.0'
    implementation "androidx.media3:media3-ui:$media3_version"
    implementation "androidx.media3:media3-exoplayer:$media3_version"
    implementation "androidx.media3:media3-exoplayer-hls:$media3_version"
    implementation "androidx.media3:media3-exoplayer-dash:$media3_version"
    implementation 'androidx.mediarouter:mediarouter:1.7.0'
    implementation 'com.google.ads.interactivemedia.v3:interactivemedia:3.36.0'

IMA DAI SDK 통합

  1. videoplayerapp 패키지(app/java/com.google.ads.interactivemedia.v3.samples/videoplayerapp/)에 SampleAdsWrapper라는 새 클래스를 만들어 기존 SampleVideoPlayer를 래핑하고 IMA DAI를 구현하는 로직을 추가합니다. 이렇게 하려면 먼저 DAI 스트림을 요청하는 데 사용되는 AdsLoader를 만들어야 합니다.

    이 스니펫에는 HLS 및 DASH, 라이브 및 VOD 스트림의 샘플 매개변수가 포함되어 있습니다. 재생 중인 스트림을 설정하려면 CONTENT_TYPE 변수를 업데이트합니다.

    package com.google.ads.interactivemedia.v3.samples.videoplayerapp;
    
    import android.annotation.SuppressLint;
    import android.content.Context;
    import android.view.ViewGroup;
    import android.webkit.WebView;
    import androidx.annotation.Nullable;
    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.StreamDisplayContainer;
    import com.google.ads.interactivemedia.v3.api.StreamManager;
    import com.google.ads.interactivemedia.v3.api.StreamRequest;
    import com.google.ads.interactivemedia.v3.api.StreamRequest.StreamFormat;
    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 com.google.ads.interactivemedia.v3.samples.samplevideoplayer.SampleVideoPlayer.SampleVideoPlayerCallback;
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    
    /** This class adds ad-serving support to Sample HlsVideoPlayer */
    @SuppressLint("UnsafeOptInUsageError")
    /* @SuppressLint is needed for new media3 APIs. */
    public class SampleAdsWrapper
        implements AdEvent.AdEventListener, AdErrorEvent.AdErrorListener, AdsLoader.AdsLoadedListener {
    
      // Live HLS stream asset key.
      private static final String TEST_HLS_ASSET_KEY = "c-rArva4ShKVIAkNfy6HUQ";
    
      // Live DASH stream asset key.
      private static final String TEST_DASH_ASSET_KEY = "PSzZMzAkSXCmlJOWDmRj8Q";
    
      // VOD HLS content source and video IDs.
      private static final String TEST_HLS_CONTENT_SOURCE_ID = "2548831";
      private static final String TEST_HLS_VIDEO_ID = "tears-of-steel";
    
      // VOD DASH content source and video IDs.
      private static final String TEST_DASH_CONTENT_SOURCE_ID = "2559737";
      private static final String TEST_DASH_VIDEO_ID = "tos-dash";
    
      private static final String NETWORK_CODE = "21775744923";
    
      private static final String PLAYER_TYPE = "DAISamplePlayer";
    
      private enum ContentType {
        LIVE_HLS,
        LIVE_DASH,
        VOD_HLS,
        VOD_DASH,
      }
    
      // Set CONTENT_TYPE to the associated enum for the stream type you would like to test.
      private static final ContentType CONTENT_TYPE = ContentType.VOD_HLS;
    
      /** Log interface, so we can output the log commands to the UI or similar. */
      public interface Logger {
        void log(String logMessage);
      }
    
      private final ImaSdkFactory sdkFactory;
      private AdsLoader adsLoader;
      private StreamManager streamManager;
      private final List<VideoStreamPlayer.VideoStreamPlayerCallback> playerCallbacks;
    
      private final SampleVideoPlayer videoPlayer;
      private final Context context;
      private final ViewGroup adUiContainer;
    
      private String fallbackUrl;
      private Logger logger;
    
      /**
       * Creates a new SampleAdsWrapper that implements IMA direct-ad-insertion.
       *
       * @param context the app's context.
       * @param videoPlayer underlying HLS video player.
       * @param adUiContainer ViewGroup in which to display the ad's UI.
       */
      public SampleAdsWrapper(Context context, SampleVideoPlayer videoPlayer, ViewGroup adUiContainer) {
        this.videoPlayer = videoPlayer;
        this.context = context;
        this.adUiContainer = adUiContainer;
        sdkFactory = ImaSdkFactory.getInstance();
        playerCallbacks = new ArrayList<>();
        createAdsLoader();
      }
    
      private void enableWebViewDebugging() {
        WebView.setWebContentsDebuggingEnabled(true);
      }
    
      private void createAdsLoader() {
        enableWebViewDebugging();
        VideoStreamPlayer videoStreamPlayer = createVideoStreamPlayer();
        StreamDisplayContainer displayContainer =
            ImaSdkFactory.createStreamDisplayContainer(adUiContainer, videoStreamPlayer);
        videoPlayer.setSampleVideoPlayerCallback(createSampleVideoPlayerCallback());
        adsLoader =
            sdkFactory.createAdsLoader(context, MyActivity.getImaSdkSettings(), displayContainer);
      }
    
      public void requestAndPlayAds() {
        adsLoader.addAdErrorListener(this);
        adsLoader.addAdsLoadedListener(this);
        adsLoader.requestStream(buildStreamRequest());
      }
    
  2. VideoStreamPlayer.VideoStreamPlayerCallback를 확장하는 SampleVideoPlayerCallback 인터페이스 인스턴스 만들기를 처리하는 createSampleVideoPlayerCallback() 도우미 메서드를 만듭니다.

    DAI를 사용하려면 플레이어가 라이브 스트림의 ID3 이벤트를 IMA DAI SDK에 전달해야 합니다. 다음 샘플 코드에서 callback.onUserTextReceived() 메서드가 이를 실행합니다.

    private SampleVideoPlayerCallback createSampleVideoPlayerCallback() {
      return new 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 we would seek past an ad, and if so, jump back to it.
          long newSeekPositionMs = positionMs;
          if (streamManager != null) {
            CuePoint prevCuePoint = streamManager.getPreviousCuePointForStreamTimeMs(positionMs);
            if (prevCuePoint != null && !prevCuePoint.isPlayed()) {
              newSeekPositionMs = prevCuePoint.getStartTimeMs();
            }
          }
          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);
          }
        }
      };
    }
    
  3. SteamRequest를 만드는 buildStreamRequest() 메서드를 추가합니다. 이 메서드는 CONTENT_TYPE 변수를 설정하는 방식에 따라 서로 다른 스트림 간에 전환합니다. 이 가이드에서 사용되는 기본 스트림은 IMA의 샘플 VOD HLS 스트림입니다.

    @Nullable
    private StreamRequest buildStreamRequest() {
      StreamRequest request;
      switch (CONTENT_TYPE) {
        case LIVE_HLS:
          // Live HLS stream request.
          return sdkFactory.createLiveStreamRequest(TEST_HLS_ASSET_KEY, null, NETWORK_CODE);
        case LIVE_DASH:
          // Live DASH stream request.
          return sdkFactory.createLiveStreamRequest(TEST_DASH_ASSET_KEY, null, NETWORK_CODE);
        case VOD_HLS:
          // VOD HLS request.
          request =
              sdkFactory.createVodStreamRequest(
                  TEST_HLS_CONTENT_SOURCE_ID, TEST_HLS_VIDEO_ID, null, NETWORK_CODE);
          request.setFormat(StreamFormat.HLS);
          return request;
        case VOD_DASH:
          // VOD DASH request.
          request =
              sdkFactory.createVodStreamRequest(
                  TEST_DASH_CONTENT_SOURCE_ID, TEST_DASH_VIDEO_ID, null, NETWORK_CODE);
          request.setFormat(StreamFormat.DASH);
          return request;
      }
      // Content type not selected.
      return null;
    }
    
  4. 스트림을 재생하려면 VideoStreamPlayer도 필요하므로 VideoStreamPlayer를 구현하는 익명 클래스를 만드는 createVideoStreamPlayer() 메서드를 추가합니다.

    private VideoStreamPlayer createVideoStreamPlayer() {
      return new VideoStreamPlayer() {
        @Override
        public void loadUrl(String url, List<HashMap<String, String>> subtitles) {
          videoPlayer.setStreamUrl(url);
          videoPlayer.play();
        }
    
        @Override
        public void pause() {
          // Pause player.
          videoPlayer.pause();
        }
    
        @Override
        public void resume() {
          // Resume player.
          videoPlayer.play();
        }
    
        @Override
        public int getVolume() {
          // Make the video player play at the current device volume.
          return 100;
        }
    
        @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.
          if (videoPlayer != null) {
            videoPlayer.enableControls(true);
          }
          log("Ad Break Ended\n");
        }
    
        @Override
        public void onAdPeriodStarted() {
          log("Ad Period Started\n");
        }
    
        @Override
        public void onAdPeriodEnded() {
          log("Ad Period Ended\n");
        }
    
        @Override
        public void seek(long timeMs) {
          // An ad was skipped. Skip to the content time.
          videoPlayer.seekTo(timeMs);
          log("seek");
        }
    
        @Override
        public VideoProgressUpdate getContentProgress() {
          return new VideoProgressUpdate(
              videoPlayer.getCurrentPositionMs(), videoPlayer.getDuration());
        }
      };
    }
    
  5. 필요한 리스너를 구현하고 오류 처리 지원을 추가합니다.

    광고가 재생되지 않으면 대체 URL을 호출하므로 AdErrorListener 구현에 유의하세요. 콘텐츠와 광고가 하나의 스트림에 있으므로 DAI 스트림에 오류가 발생하면 대체 스트림을 호출할 준비가 되어 있어야 합니다.

    /** AdErrorListener implementation */
    @Override
    public void onAdError(AdErrorEvent event) {
      log(String.format("Error: %s\n", event.getError().getMessage()));
      // play fallback URL.
      log("Playing fallback Url\n");
      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 will be 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;
    }
    
  6. 로깅 코드를 추가합니다.

    /** 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);
      }
    }
  7. SampleAdsWrapper를 인스턴스화하고 호출하도록 videoplayerappMyActivity를 수정합니다. 또한 여기에서 도우미 메서드를 사용하여 ImaSdkFactory.initialize()를 호출하여 ImaSdkSettings 인스턴스를 만듭니다.

    package com.google.ads.interactivemedia.v3.samples.videoplayerapp;
    
    import android.annotation.SuppressLint;
    import android.app.Activity;
    import android.content.res.Configuration;
    import android.os.Bundle;
    import android.util.Log;
    import android.view.View;
    import android.widget.ImageButton;
    import android.widget.ScrollView;
    import android.widget.TextView;
    import com.google.ads.interactivemedia.v3.api.ImaSdkFactory;
    import com.google.ads.interactivemedia.v3.api.ImaSdkSettings;
    import com.google.ads.interactivemedia.v3.samples.samplevideoplayer.SampleVideoPlayer;
    
    /** Main Activity that plays media using {@link SampleVideoPlayer}. */
    @SuppressLint("UnsafeOptInUsageError")
    /* @SuppressLint is needed for new media3 APIs. */
    public class MyActivity extends Activity {
    
      private static final String DEFAULT_STREAM_URL =
          "https://storage.googleapis.com/interactive-media-ads/media/bbb.m3u8";
      private static final String APP_LOG_TAG = "ImaDaiExample";
      private static final String PLAYER_TYPE = "DAISamplePlayer";
      private static ImaSdkSettings imaSdkSettings;
    
      protected SampleVideoPlayer sampleVideoPlayer;
      protected ImageButton playButton;
    
      private boolean contentHasStarted = false;
    
      @Override
      protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_my);
    
        // Initialize the IMA SDK as early as possible when the app starts. If your app already
        // overrides Application.onCreate(), call this method inside the onCreate() method.
        // https://developer.android.com/topic/performance/vitals/launch-time#app-creation
        ImaSdkFactory.getInstance().initialize(this, getImaSdkSettings());
    
        View rootView = findViewById(R.id.videoLayout);
        sampleVideoPlayer =
            new SampleVideoPlayer(rootView.getContext(), rootView.findViewById(R.id.playerView));
        sampleVideoPlayer.enableControls(false);
        playButton = rootView.findViewById(R.id.playButton);
        final SampleAdsWrapper sampleAdsWrapper =
            new SampleAdsWrapper(this, sampleVideoPlayer, rootView.findViewById(R.id.adUiContainer));
        sampleAdsWrapper.setFallbackUrl(DEFAULT_STREAM_URL);
    
        final ScrollView scrollView = findViewById(R.id.logScroll);
        final TextView textView = findViewById(R.id.logText);
    
        sampleAdsWrapper.setLogger(
            logMessage -> {
              Log.i(APP_LOG_TAG, logMessage);
              if (textView != null) {
                textView.append(logMessage);
              }
              if (scrollView != null) {
                scrollView.post(() -> scrollView.fullScroll(View.FOCUS_DOWN));
              }
            });
    
        // Set up play button listener to play video then hide play button.
        playButton.setOnClickListener(
            view -> {
              if (contentHasStarted) {
                sampleVideoPlayer.play();
              } else {
                contentHasStarted = true;
                sampleVideoPlayer.enableControls(true);
                sampleAdsWrapper.requestAndPlayAds();
              }
              playButton.setVisibility(View.GONE);
            });
        orientVideoDescription(getResources().getConfiguration().orientation);
      }
    
  8. getImaSdkSettings() 도우미 메서드를 추가하여 ImaSdkSettings 인스턴스를 만듭니다.

    public static ImaSdkSettings getImaSdkSettings() {
      if (imaSdkSettings == null) {
        imaSdkSettings = ImaSdkFactory.getInstance().createImaSdkSettings();
        imaSdkSettings.setPlayerType(PLAYER_TYPE);
        // Set any additional IMA SDK settings here.
      }
      return imaSdkSettings;
    }
  9. 로깅을 위한 UI 요소를 추가하도록 Activity의 레이아웃 파일 activity_my.xml을 수정합니다.

    <!-- 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를 다운로드해 보세요. BasicExample에서 제대로 작동하면 앱의 IMA 통합 코드에 문제가 있을 수 있습니다.

문제가 계속되면 IMA SDK 포럼을 방문하세요.