본문으로 건너뛰기

동영상 광고

동영상 광고(InStream Video Ad)는 동영상 컨텐츠 재생 전, 재생 중 또는 재생 후에 삽입되는 광고형태입니다.

본 페이지는 전/중/후 광고 호출을 위한 비디오 스케줄 매니저 GfpVideoAdScheduleManager (이하 VSM) 사용 가이드입니다.

동영상 광고가 재생될 Player 는 서비스가 사용하는 Player 를 주입받는 형태입니다.

플레이어 설정을 참고해 주시기 바랍니다.


[Step 1] Dependency 추가

dependencies {
implementation(platform("com.naver.gfpsdk:nam-bom:8.1.0"))
implementation("com.naver.gfpsdk:nam-core")
implementation("com.naver.gfpsdk:nam-ndavideo") // Naver InStream ads extension
}

[Step 2] Player 레이아웃이 포함될 수 있는 ViewGroup 추가

비디오 광고를 게재하기 위해서는 먼저 광고를 게재하려는 Activity 또는 Fragment 의 레이아웃에

비디오 광고와 본영상이 재생될 수 있는 영역에 대한 ViewGroup 을 추가해야 합니다.

아래 예제에서는 RelativeLayout ViewGroup 에 video_ad_container 라는 id 로 비디오 광고가 게재될 Layout 을 선언했습니다.

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">

<RelativeLayout
android:id="@+id/video_ad_container"
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="#000000"
app:layout_constraintDimensionRatio="16:9"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

[Step 3] Player 와 광고 UI 가 포함될 레이아웃 생성

비디오 광고가 재생되는데 필요한 Player(AdVideoPlayer 에 대한 구현이 되어있어야 함) 와 광고 UI 가 포함될 레이아웃을 선언합니다.

아래는 player 와 광고 ui 가 포함될 frame layout 을 포함하는 레이아웃에 대한 예시 이며,

생성된 ad_video_playerad_ui_container 는 광고 로더인 GfpVideoAdScheduleManager의 생성자에서 사용됩니다.

아래에서 생성한 xml 파일의 이름은 video_ad_exoplayer_layout.xml 이라고 가정합니다.

outer_text_ad_container의 경우 SMR 광고에서 사용되는 리마인드 광고에 사용되는 영역이고 아래와 같이 별도로 설정하게 됩니다.

videoAdScheduleManager.setOuterRemindTextAdViewContainer(outerTextAdContainer);

다만 이번 샘플에서는 사용되지 않습니다.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">

<com.naver.gfpsdk.adssample.video.SampleExoPlayerView
android:id="@+id/ad_video_player"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerHorizontal="true" />

<FrameLayout
android:id="@+id/ad_ui_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />

<FrameLayout
android:id="@+id/outer_text_ad_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</RelativeLayout>

[Step 4] AdParam 과 VideoAdScheduleParam 생성

비디오 광고 요청을 위한 광고 파라미터(AdParam) 와 스케줄 파라미터(VideoAdScheduleParam)를 구성합니다.

4-1. AdParam

광고 요청 정보 를 참고하여 광고 요청에 대한 AdParam 를 생성합니다.

다른 광고와는 다르게, Ad Unit ID는 필수 값이 아니고, 동영상 관련 파라미터를 필수로 받게 됩니다. 위 'AdParam' 정보 페이지에서 동영상 관련 파라미터를 참고해 주세요.

AdParam 구성시, setAdUnitId 설정이 불필요하며 설정시 무시 됩니다.

4-2 VideoAdScheduleParam
  • 컨텐츠 길이(contentDuration)

필수 입력값 이며, 초단위로 정확히 입력해야 스케줄이 정상 동작 합니다.

  • 스케줄아이디(adScheduleId)

필수값이며 NAM 담당자로 부터 사전에 안내 받아야 합니다.

  • 스케줄 정책 설정(setAdSchedulePolicy)

전/중/후 광고를 선택적으로 제외할 수 있습니다.

부가적인 값으로, 각 스케쥴 아이디마다 NAM 어드민에서 설정한 정책이 있습니다.

어드민에 설정은 했지만 서비스에서 off 를 원할 때 사용됩니다.

  • (라이브 컨텐츠의 경우) 컨텐츠 시작 시점(setContentStartOffset)

컨텐츠 시작 시점 기준으로 광고 스케줄 오프셋이 조정됩니다.

val adParam = AdParam.Builder()
.setRefererPageUrl("https://tv.naver.com/r/")
.setCurrentPageUrl("https://tv.naver.com/v/36037577/list/67096")
.setVrr(1) // SMR 광고에서 Remind Ad를 요청할지 (1 = request, 0 = don't request)
.addCustomParam("lo", "Y") // Loudness Normalization 적용 여부
.addCustomParam("pt", "635") // 콘텐츠 클립 전체 시간 (second 단위)
.addCustomParam("pcmo", "mo") // pc or mo or tv - mo로 고정된 값 사용
.addCustomParam("vid", "0A7CFF5F2F5791DEE667757BD6F9E80411AA") // video ID
.addCustomParam("tid", "3Cb51Nn2ufoyPok6Tpct-A") // 트랜잭션 ID
.addCustomParam("cp", "1403") // 제휴사(창작자) 구분 ID
.addCustomParam("chl", "motline") // 채널 구분 ID
.addCustomParam("cl", "20539479") // 클립 ID
.addCustomParam("svc", "WAVVE_AOS") // 서비스 ID - 고정된 값 (NAM 담당자 통해 확인해 주세요.)
.addCustomParam("cc", "N") // 어린이 보호 콘텐츠 적용 여부
.addCustomParam("AreaId", "clip") // 광고 영역
.build()
/** 이하는 스케줄 정보 수신 이후 SDK 에서 설정합니다.
* 스케줄과 별도로 설정하고자 할 때만 사용해 주세요.
* .setVcl() // 비디오 켄텐츠 길이
* .setVri() // 비디오 스케줄 요청 아이디
* .setVrr() // 리마인드 광고 여부
* .setVsd() // 비디오 전/중/후 여부
* .setVsi() // 비디오 스케줄 아이디
*/
val contentDuration = 653L // 초 단위의 컨텐츠 영상 길이
val adScheduleId = "WAVVE_SCH" // Ad Unit ID 처럼 NAM 담당자에게 안내 받아야 함
val pre = true
val mid = false // 영상 중간에 광고 없음 선택
val post = true

val videoAdScheduleParam = VideoAdScheduleParam.Builder(adScheduleId)
.setDuration(contentDuration)
.setAdSchedulePolicy(pre, mid, post)
.setAdNoticeDurationSec(5) // mid 광고 시작 전 안내 문구 표시 시간
.build()
4-3 비디오 광고 옵션 설정

GfpVideoAdScheduleManager setVideoAdOptions() 을 통해 비디오 광고 옵션을 설정합니다.

GfpVideoAdOptions 에는 bitrateKbps, videoLoadTimeout(ms), hls 지원여부를 설정할 수 있습니다.

  • hls 는 기본 미지원입니다.

    ( video/mp4만 지원 )

  • bitrateKbps 로 광고의 품질을 결정합니다.

    기본값은 -1로 IMA 의 경우 자동 셋팅, 네이버 광고의 경우에는 최저 (hls 미지원 시) 해상도의 광고가 재생됩니다.

  • videoLoadTimeout 은 재생 start 요청 후 해당 시간까지 실제 재생이되지 않으면 에러를 발생시킵니다.

    기본값은 5초(5000ms)

val videoAdOptions = GfpVideoAdOptions().apply {
supportedStreamingHLS = false
bitrateKbps = -1
videoLoadTimeout = 5000
}

videoAdManager.setVideoAdOptions(videoAdOptions)

[Step 5] GfpVideoAdScheduleManager 생성

동영상 광고 호출을 위한 GfpVideoAdScheduleManager 를 생성합니다.

위에서 설정한 광고 파라미터(AdParam) 와 스케줄 파라미터(VideoAdScheduleParam)를 GfpVideoAdScheduleManager 생성자에 함께 전달합니다.

추가로 위에서 생성한 layout 을 inflate 하여서 player 레이아웃을 포함시키기 위해서 만들어 놓은 video_ad_container 에 add 합니다.

예시는 아래 광고 로드를 참고해 주세요.


[Step 6] 스케줄 이벤트 수신

동영상 광고 스케줄 이벤트는 VideoAdScheduleListener interface 를 통해서 수신할 수 있습니다.

각 비디오 광고 스케쥴 한건마다 이벤트를 처리하므로, 광고 시작/종료시 본 컨텐츠의 시작/종료를 해당 리스너를 통해 처리해야 합니다.

정보

광고가 로드되면 본 영상은 멈추고, 광고 영상 에러 처리시 본 영상이 재생 되도록 처리해 주셔야 합니다.

videoAdScheduleManager.setAdScheduleListener(object: VideoAdScheduleListener {
override fun onScheduleLoaded(schedule: VideoScheduleResponse) {
// 광고 스케줄이 로드 완료 되었을 때 실행됩니다.
}

override fun onContentResumeRequest() {
// 컨텐츠를 재시작 할 때 실행됩니다.
}

override fun onContentPauseRequest() {
// 컨텐츠를 일시정지 할 때 실행됩니다.
}

override fun onScheduleCompleted() {
// 모든 광고의 재생이 완료되었을 때 실행됩니다.
}

override fun onError(error: GfpError) {
// 광고 요청이 실패했을 때 실행됩니다.
}
})

[Step 7] 광고 이벤트 수신

동영상 광고의 생명주기(로드, 재생 준비, 재생 시작, 비선형 광고 준비, 클릭, 재생 완료, 에러) 에 대한 이벤트는 VideoAdListener interface 를 통해서 수신할 수 있습니다.

videoAdScheduleManager.setAdListener(object: VideoAdListener {
override fun onAdLoaded(ad: GfpVideoAd) {
// 광고 load 가 성공했을 때 실행됩니다.
}

override fun onAdStartReady(ad: GfpVideoAd) {
// 광고 재생이 필요한 시점에 도달했을 때 실행됩니다.
}

override fun onAdStarted(ad: GfpVideoAd) {
// 광고 재생이 시작했을 때 실행됩니다.
}

override fun onAdNonLinearStartReady(ad: GfpVideoAd) {
// 비선형 광고 노출 시점에 도달했을 때 실행됩니다.
}

override fun onAdClicked(ad: GfpVideoAd) {
// 광고 click 이 발생했을 때 실행됩니다.
}

override fun onAdCompleted(ad: GfpVideoAd) {
// 광고 재생이 완료됐을 때 실행됩니다.
}

override fun onError(ad: GfpVideoAd, error: GfpError) {
// 광고 요청이 실패했을 때 실행됩니다.
}
})
7-1. QOE(Quality of Experience) 정보

동영상 광고 재생과 관련한 더 상세한 이벤트를 얻기위해 GfpVideoAdScheduleManagersetQoeListener 를 통해 이벤트 정보를 받을 수 있습니다.

videoAdManager.setQoeListener(object: GfpVideoAdQoeListener {
override fun onAdLoaded(info: GfpVideoAdQoeInfo) {
Log.d("MainActivity", String.format("GfpVideoAdQoeListener.onAdLoaded \tinfo:\n\t\tplacement: %s\n\t\tprovider: %s \n\t\tmedia_h: %d\n\t\tmedia_w: %d \n\t\tcurrent time: %f\n\t\tduration: %f\n\t\toffset: %f",
info.placementType,
info.provider,
info.mediaHeight,
info.mediaWidth,
info.currentTime,
info.duration,
info.skipOffset
))
}

override fun onAdStarted(info: GfpVideoAdQoeInfo) {
Log.d("MainActivity", "GfpVideoAdQoeListener.onAdStarted")
}

override fun onAdPaused(info: GfpVideoAdQoeInfo) {
Log.d("MainActivity", "GfpVideoAdQoeListener.onAdPaused")
}

override fun onAdSkipped(info: GfpVideoAdQoeInfo) {
Log.d("MainActivity", "GfpVideoAdQoeListener.onAdSkipped")
}

override fun onAdResumed(info: GfpVideoAdQoeInfo) {
Log.d("MainActivity", "GfpVideoAdQoeListener.onAdResumed")
}

override fun onAdClicked(info: GfpVideoAdQoeInfo) {
Log.d("MainActivity", "GfpVideoAdQoeListener.onAdClicked")
}

override fun onAdCompleted(info: GfpVideoAdQoeInfo) {
Log.d("MainActivity", "GfpVideoAdQoeListener.onAdCompleted")
}

override fun onError(error: GfpError) {
Log.d("MainActivity", "GfpVideoAdQoeListener.onError")
}
})

[Step 8] 타임아웃 설정

GfpVideoAdScheduleManager 를 통해 광고 요청마다 타임아웃을 설정할 수 있습니다.

GfpVideoAdScheduleManager 객체를 통해 별도의 타임아웃을 설정하지 않을 경우,

기본값은 SdkProperties 를 통해 전역 설정된 값(기본값 : 60초)을 사용합니다.

videoAdScheduleManager.setGfpVideoProperties(GfpVideoProperties(60_000L, null))

[Step 9] 광고 로드 (샘플)

GfpVideoAdScheduleManager 에 대한 설정이 마무리 되었다면 광고를 로드할 수 있습니다.

다음은 Activity 의 onCreate() method 에서 광고를 로드하는 방법을 보여주는 예시입니다.

package ...

import ...

class MainActivity : AppCompatActivity() {
private lateinit var playerContainer: RelativeLayout
private lateinit var adVideoPlayer: AdVideoPlayer
private lateinit var videoAdScheduleManager: GfpVideoAdScheduleManager

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

playerContainer = findViewById(R.id.video_ad_container)

// VSM 을 사용하는 경우, setAdUnitId 설정이 불필요하며 설정 시 무시됩니다.
val adParam = AdParam.Builder().build()

// 위에서 선언한 video_ad_exoplayer_layout inflate.
val playerLayout = layoutInflater.inflate(R.layout.video_ad_exoplayer_layout, null, false)

// 본영상과 광고영상 재생을 위한 Sample player 를 가정.
val exoPlayerView = playerLayout.findViewById<SampleExoPlayerView>(R.id.ad_video_player)
val adUiContainer = playerLayout.findViewById<FrameLayout>(R.id.ad_ui_container)

// SampleExoPlayerView 에서 AdVideoPlayer 를 포함하고 있는 형태로 구현했다고 가정.
adVideoPlayer = exoPlayerView.createAdVideoPlayer("YOUR_CONTENTS_URL")

// Schedule Param 설정
val contentDuration: Long = ***L // 컨텐츠 본영상의 길이를 '초' 단위로 정확히 입력해야 스케줄이 정상 동작 합니다.
val adScheduleId = "VIDEO_AD_SCHEDULE_ID" // 스케줄아이디는 광고 기획 담당자로 부터 사전에 안내 받아야 합니다.
val videoAdScheduleParam = VideoAdScheduleParam.Builder(adScheduleId)
.setDuration(contentDuration)
.setAdSchedulePolicy(true, false, true)
.setAdNoticeDurationSec(5)
.build()

// manager 생성
videoAdScheduleManager = GfpVideoAdScheduleManager(
this,
videoAdScheduleParam,
adParam,
adVideoPlayer,
adUiContainer
)

// 비디오 옵션 설정
val videoAdOptions = GfpVideoAdOptions()
videoAdOptions.setSupportedStreamingHLS(true)
videoAdScheduleManager.setVideoAdOptions(videoAdOptions)

/**
* 필요시 QOE 리스너 설정
videoAdScheduleManager.setQoeListener(object: GfpVideoAdQoeListener {
override fun onAdLoaded(info: GfpVideoAdQoeInfo) {
...
}
...
})
*/

// Naver 서비스이면서 공유 기능을 사용 한다면...
// videoAdScheduleManager.setAdvertiseParams(naver_share_uri_param_json_string);
// Naver 서비스이면서 SMR 광고를 서빙한다면, remind text 관련 설정 필요
// videoAdScheduleManager.setOuterRemindTextAdViewContainer(outerTextAdContainer);

// 스케쥴 리스너 설정
videoAdScheduleManager.setAdScheduleListener(object: VideoAdScheduleListener {
override fun onScheduleLoaded(schedule: VideoScheduleResponse) {
Log.d("MainActivity", String.format("onScheduleLoaded - AdBreak size: %d", schedule.adBreaks.size))
}

override fun onContentResumeRequest() {
Log.d("MainActivity", "컨텐츠 재생")
// 본 영상 재생
if (exoPlayerView.isPlaying.not()) {
exoPlayerView.resumeContentsRequest()
}
}

override fun onContentPauseRequest() {
Log.d("MainActivity", "컨텐츠 일시정지")
exoPlayerView.pauseContentsRequest()
}

override fun onScheduleCompleted() {
Log.d("MainActivity", "스케줄 내 모든 광고 완료")
// 본 영상 재생
if (exoPlayerView.isPlaying.not()) {
exoPlayerView.resumeContentsRequest()
}
}

override fun onError(gfpError: GfpError) {
Log.d("MainActivity", "스케줄 광고 요청 에러 Error: $gfpError")
// 본 영상 재생
if (exoPlayerView.isPlaying.not()) {
exoPlayerView.resumeContentsRequest()
}
}
})

// 광고 응답 정보 리스너 설정
videoAdScheduleManager.setAdListener(object: VideoAdListener {
override fun onAdLoaded(ad: GfpVideoAd) {
// NonLinearAdInfo nonLinearAdInfo = ad.getNonLinearAdInfo();
// remind 배너 정보 확인 가능
Log.d("MainActivity", "onAdLoaded()\tResponseInfo: ${ad.responseInfo}")
}

override fun onAdStartReady(ad: GfpVideoAd) {
ad.start(true)
Log.d("MainActivity", "광고 재생 준비 완료")
}

override fun onAdNonLinearStartReady(ad: GfpVideoAd) {
Log.d("MainActivity", "Non Linear 광고 재생 준비 완료")
// NonLinear 광고 사용 시 구현 및 확인은 SDK 담당과 논의해 주세요.
ad.showNonLinearAd(GfpNonLinearAdView.ContainerType.INNER)
}

override fun onAdStarted(ad: GfpVideoAd) {
Log.d("MainActivity", "광고 재생")
}

override fun onAdClicked(ad: GfpVideoAd) {
Log.d("MainActivity", "클릭 발생")
}

override fun onAdCompleted(ad: GfpVideoAd) {
Log.d("MainActivity", "광고 재생 완료")

// 본 영상 재생
if (exoPlayerView.isPlaying.not()) {
exoPlayerView.resumeContentsRequest()
}
}

override fun onError(ad: GfpVideoAd, error: GfpError) {
Log.e("MainActivity", "에러 발생\t" +
String.format(
"code[%d] subCode[%s] message[%s] responseInfo[%s]",
error.errorCode,
error.errorSubCode,
error.errorMessage,
ad.responseInfo.toString()
)
)

// 본 영상 재생
if (!exoPlayerView.isPlaying) {
exoPlayerView.resumeContentsRequest()
}
}
})

playerContainer.addView(playerLayout)
videoAdScheduleManager.load()
}

override fun onPause() {
super.onPause()
if (::videoAdScheduleManager.isInitialized) {
videoAdScheduleManager.pause()
}
}

override fun onResume() {
super.onResume()
if (::videoAdScheduleManager.isInitialized) {
videoAdScheduleManager.resume()
}
}

override fun onDestroy() {
super.onDestroy()

if (::videoAdScheduleManager.isInitialized) {
videoAdScheduleManager.destroy()
}
}
}

[Step 10] 광고 삭제

스케줄에 등록된 모든 광고가 종료되었거나 광고 화면을 벗어나는 경우 광고가 올바르게 폐기되도록 스케줄 매니저를 제거해 주어야 합니다.

스케줄 매니저 제거는 아래와 같이 GfpVideoAdScheduleManager 가 제공하는 destory() 를 호출하면 됩니다.

if (::videoAdScheduleManager.isInitialized) {
videoAdScheduleManager.destroy()
}