動画広告への対応

動画対応SDKで提供されるコンポーネント

VideoAdView: 動画再生およびトラッキングイベントの送信を行うCustom Viewです。 レイアウトファイルに直接配置することができます。

VideoAdActivity: 全画面時に表示されるActivityです。 VideoAdViewと同様、動画再生およびトラッキングイベントの送信を行いますが、動画再生の制御機能が増えています。

VideoAdActivityの登録

動画SDKでは全画面表示に対応するためSDKのVideoAdActivityを使用します。これをAndroidManifest.xmlに登録しておきます。

<activity android:name="jp.fout.rfp.android.sdk.video.VideoAdActivity" />

動画広告の判定

動画広告の場合、RFPInstreamInfoModel#isVideo()trueを返します。これを利用して動画広告か否かを判定することができます。

private boolean isAd(int position) {
    return getItem(position) instanceof RFPInstreamInfoModel;
}

private boolean isVideoAd(int position) {
    if (!isAd(position)) {
        return false;
    }
    RFPInstreamInfoModel item = (RFPInstreamInfoModel) getItem(position);
    return item.isVideo();
}

動画広告情報をVideoAdViewに処理させる

動画広告の詳細情報はRFPInstreamInfoModelに格納されており、これを元に動画広告を組み立てるためVideoAdViewに処理させる必要があります。 具体的な実装としてはVideoAdView#processAd()RFPInstreamInfoModelのインスタンスに渡します。

static class AdViewHolder {
    TextView advertiserName;
    TextView adText;
    ImageView adImage;
    TextView adSponsoredLabel;
    VideoAdView adVideo;

    AdViewHolder(View convertView) {
        advertiserName = (TextView) convertView.findViewById(R.id.custom_instream_advertiser_name);
        adText = (TextView) convertView.findViewById(R.id.custom_instream_ad_text);
        adImage = (ImageView) convertView.findViewById(R.id.custom_instream_ad_image);
        adSponsoredLabel = (TextView) convertView.findViewById(R.id.custom_instream_sponsor_name);
        adVideo = (VideoAdView) convertView.findViewById(R.id.custom_instream_video_view);
    }

    void setData(RFPInstreamInfoModel adData) {
        advertiserName.setText(adData.title());
        adText.setText(adData.content());

        String displayedAdvertiser = adData.displayedAdvertiser();
        if (null != displayedAdvertiser && 0 < displayedAdvertiser.length()) {
            adSponsoredLabel.setText(displayedAdvertiser);
        }

        adVideo.processAd(adData);
    }
}

動画広告に配置するボタンのテキストの変更

動画広告に配置する配置するボタンのテキストは広告の管理画面から設定することができます。 この設定値はRFPInstreamInfoModel#cta_text()で取得することができます。

static class AdViewHolder {
    // ... ...
    Button adButton;

    AdViewHolder(View convertView) {
        // ... ...
        adButton = (Button) convertView.findViewById(R.id.custom_instream_action_button);
    }

    void setData(RFPInstreamInfoModel adData) {
        // ... ...
        String adButtonText = adData.cta_text();
        if (adButtonText != null && adButtonText.length() > 0) {
            adButton.setText(adButtonText);
        }

        adVideo.processAd(adData);
    }
}

動画の再生

VideoAdView#processAd()を呼び出し、動画が再生可能となった時点で自動的に再生が開始されます。

VideoAdView#setAutoStart(false)とすることで自動再生を抑制することができます。この場合、VideoAdView#play()をアプリから呼び出し再生を開始する必要があります。また、VideoAdView#pause()を呼び出すことにより任意の地点で停止することができます。

この場合、動画の再生・停止のタイミングはアプリケーション開発者に任せられますが、ディスプレイ枠などである程度動画の再生・停止を自動化したい場合のため、 RFPInViewNotifier というユーティリティクラスを用意してあります。

実装手順は以下のようになります:

  1. 自動再生抑止のため VideoAdView#setAutoStart(false) を呼ぶ
  2. RFPInViewNotifier のインスタンスを生成する。 RFPInViewNotifier.OnVisibilityChangedListener を実装し、 onVisible()VideoAdView#play()onInVisible()VideoAdView#pause() を呼ぶ。
  3. RFPInViewNotifier#addView(View) で広告枠を監視下に入れる。
  4. ライフサイクル終了時に RFPInViewNotifier#detach() を呼ぶ。

以下に実装例を示します:

private lateinit var adVideoView: VideoAdView
private lateinit var inViewNotifier: RFPInViewNotifier

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                          savedInstanceState: Bundle?): View? {
    val view = inflater.inflate(R.layout.fragment_outstream, container, false)

    adVideoView = view.findViewById(R.id.ad_video)
    adVideoView.setAutoStart(false) // 1

    // 2
    inViewNotifier = RFPInViewNotifier(requireActivity(), object : RFPInViewNotifier.OnVisibilityChangedListener {
        override fun onVisible(v: View?) {
            (v as? VideoAdView)?.play()
        }

        override fun onInvisible(v: View?) {
            (v as? VideoAdView)?.pause()
        }
    })

    // 3.
    inViewNotifier.addView(adVideoView)

    // ... ...

    return view
}

override fun onDestroyView() {
    super.onDestroyView()

    inViewNotifier.detach() // 4

    // ... ...
}

全画面モードへの移行

動画領域をタップすることで全画面表示となります。

この挙動はデフォルトの動作ですが、全画面モードを終了した際に動画の再生位置を同期させたい場合は追加の実装が必要となります。

全画面モードはActivityであり、再生位置およびトラッキングイベント送信状況の引き継ぎのため、Activity#startActivityForResult()Activity#onActivityResult()で結果の受け渡しを行っています。

VideoAdViewは全画面モード移行時にOnFullscreenRequestListener#onRequestFullscreen()を呼び出しているのでこれを実装します。引数として対象となるVideoAdViewと引き継ぎ情報の入ったインテントが渡されるのでこれをもとにActivity#startActivityForResult()を呼びます。ActivityへのリクエストコードにはRFP.getVideoAdFullscreenRequestCode()を使用してください。

VideoAdView#restore(Intent)を使用することで再生位置およびトラッキングイベント送信状況を引き継ぐことができます。

ActivityがVideoAdView#restore(Intent)の対象となるVideoAdViewを知っている必要がありますが、VideoAdViewのフルスクリーン画面呼び出し時のコールバックであるVideoAdView.OnFullscreenRequestListenerを実装することでこれをActivityに通知することができます。

下記の例では、greenrobot/EventBusを用いてActivityに対象となるVideoAdViewを通知し、保存しています。

class VideoAdViewHolder extends RecyclerView.ViewHolder
        implements VideoAdView.OnFullscreenRequestListener {
    RFPInstreamAdPlacer adPlacer;
    VideoAdView adVideo;

    VideoAdViewHolder(View itemView, RFPInstreamAdPlacer placer) {
        super(itemView);
        adPlacer = placer;

        // ... ...

        adVideo = (VideoAdView) itemView.findViewById(R.id.custom_instream_ad_image);
        adVideo.setOnFullscreenRequestListener(this);
    }

    @Override
    public void onRequestFullscreen(VideoAdView view, Intent intent) {
        EventBus.getDefault().post(new RequestFullscreenEvent(view, intent));
    }
}
VideoAdView mSourceVideoAdView = null;

@Subscribe(threadMode = ThreadMode.MAIN)
public void onRequestFullscreen(RequestFullscreenEvent event) {
    mSourceVideoAdView = event.view;
    startActivityForResult(event.intent, RFP.getVideoAdFullscreenRequestCode());
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    if (requestCode == RFP.getVideoAdFullscreenRequestCode()
            && resultCode == Activity.RESULT_OK
            && mSourceVideoAdView != null) {
        mSourceVideoAdView.restore(data);
    }
}

RFP#getVideoAdFullscreenRequestCode()のデフォルト値は18416で、RFP#setVideoAdFullscreenRequestCode(int)を使用して任意の値に変更できます。

RFP.setVideoAdFullscreenRequestCode(1000);

VideoAdView#setAutoStart()を用いて自動再生を抑制している場合は、復帰時にVideoAdView#play()を呼び出す必要があります。

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    if (requestCode == RFP.getVideoAdFullscreenRequestCode()
            && resultCode == Activity.RESULT_OK
            && mSourceVideoAdView != null) {
        mSourceVideoAdView.restore(data);
        mSourceVideoAdView.play();
    }
}

モードへの移行時の再生制御

全画面モードへの移行時、アクションボタンによるランディングページの移動時は正しくトラッキングイベントを取得するため停止しておく必要があります。

全画面モード移行時と全画面モード内のアクションボタンについてはSDKで正しくハンドリングされますが、アプリ側の制御となるフィード内のアクションボタンについては動画の停止処理を記述する必要があります。VideoAdView#pause()Activity#onStop()Activity#onPause()、アクションボタンのonClick時などいずれかに記述してください。

VideoAdView adVideo = (VideoAdView) itemView.findViewById(R.id.custom_instream_ad_image);
Button adButton = (Button) itemView.findViewById(R.id.custom_instream_ad_action_button);
adButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        adVideo.pause();
        adPlacer.sendClickEvent(adData);
    }
});

全画面モード移行の抑制

デフォルトでは動画領域をタップすると全画面モードに移行しますが、VideoAdView#setOnVideoAreaClickListener(OnClickLisner)にて挙動をカスタマイズできます。

下記の例はタップ時にランディングページに遷移(Call-to-Actionと同様の挙動)となるような実装です。

RFPInstreamAdPlacer adPlacer;

VideoAdViewHolder(View itemView, RFPInstreamAdPlacer placer) {
    super(itemView);

    adPlacer = placer;
}

public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
    if (holder.getItemViewType() == ITEM_VIEW_TYPE_AD_VIDEO) {
        final VideoAdViewHolder h = (VideoAdViewHolder) holder;
        final RFPInstreamInfoModel model = (RFPInstreamInfoModel) mModels.get(position);
        h.adVideoView.setOnVideoAreaClickListener(new OnClickListener() {
            public void onClick(View v) {
                h.adVideoView.pause();
                adPlacer.sendClickEvent(model);
            }
        }
    } else {
        // ... ...
    }
}

動画広告読み込みエラーへの対応

動画広告読み込み時にエラーが発生した場合、エラー処理を記述して制御することができます。

フィード内の場合

フィード内ではVideoAdView#OnErrorListenerを実装し、VideoAdView#setOnErrorListener()にてリスナーを設定してください。

static class AdViewHolder implements VideoAdView.OnErrorListener {
    VideoAdView adVideo;

    VideoAdViewHolder(View itemView, RFPInstreamAdPlacer placer) {
        super(itemView);

        // ... ...

        adVideo = (VideoAdView) itemView.findViewById(R.id.custom_instream_ad_image);
        adVideo.setOnErrorListener(this);
    }
    @Override
    public void onError(VideoAdView view, String message, @Nullable Throwable t) {
        // view: エラーの発生したVideoAdView
        // message: エラー内容
        // t: 例外が発生した場合に該当する例外
        Log.d(TAG, message, t);
    }
}

フルスクリーンの場合

フルスクリーン時にエラーが発生した場合、アクティビティは終了し、onActivityResult()resultCodeRESULT_CANCELEDが入ります。これを判定することによりエラー処理を記述することができます。この場合、Intentにエラー内容が入ります。

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    if (requestCode == RFP.getVideoAdFullscreenRequestCode()) {
        if (resultCode == Activity.RESULT_OK) {
            // 正常時の処理
        } else if (resultCode == Activity.RESULT_CANCELED) {
            // エラー時の処理
            String message = data.getStringExtra("error_message");
            Throwable t = (Throwable) data.getSerializableExtra("error");
            Log.d(TAG, message, t);
        }
    }
}

キャッシュの設定

RFPは動画広告を内部ストレージのキャッシュ領域にある程度保持します。

キャッシュ容量はRFPが推奨する設定値を使用します。この値はアプリ側から変更することが可能です。

int size = RFP.getVideoCacheSize(); // 現在の設定値を返す
RFP.setVideoCacheSize(50); // 50MBをキャッシュ領域として使用する
RFP.setVideoCacheSize(0);  // キャッシュを無効にする

動画が画面内に移動した時の再開

動画SDKではウィンドウからデタッチされた動画の再生状態を保持していませんので、再びアタッチされた際に動画が初期状態から再生されます。 この挙動はデフォルトの動作ですが、再びウィンドウ内にアタッチされた際に対象動画を元の再生位置から開始させたい場合は追加の実装が必要となります。

VideoAdViewが画面外に移動時にVideoAdView#OnDetachedListenerを呼び出しているのでこれを実装します。引数として対象となるVideoAdViewの引き継ぎ情報の入ったBundleが渡されるのでこれを格納してください。

対象動画が画面内に移動されたら、先ほど格納したBundleを VideoAdView#processAd(RFPInstreamInfoModel adModel, @Nullable Bundle savedState)に渡して再生状態を引き継ぐことができます。

// store ad's information for resume
private SparseArray<Bundle> mResumeVideosArray = new SparseArray<>();

public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
    if (holder.getItemViewType() == ITEM_VIEW_TYPE_AD_VIDEO) {
        final VideoAdViewHolder h = (VideoAdViewHolder) holder;
        final RFPInstreamInfoModel model = (RFPInstreamInfoModel) mModels.get(position);

        // get resume ad's information if exist
        Bundle state = mResumeVideosArray.get(position);
        // remove ad's old information if exist
        mResumeVideosArray.remove(position);
        // reset ad's information when it's detached from window
        h.adVideoView.setOnDetachedListener(bundle -> mResumeVideosArray.put(position, bundle));
        h.setData(adInfo, null, state);
    } else {
        // ... ...
    }
}

static class AdViewHolder {
    VideoAdView adVideo;
    // ... ...

    AdViewHolder(View convertView) {
        adVideo = (VideoAdView) itemView.findViewById(R.id.custom_instream_ad_image);
        // ... ...
    }

    void setData(RFPInstreamInfoModel adData, Bundle savedState) {
        // ... ...
        // restart ad from the position when being detached
        adVideo.processAd(adData, savedState);
    }