Android ExoPlayer + HLS + Google IMA

はじめに

こんにちは streampack チームのメディです。
https://cloudpack.jp/service/option/streampack.html

Objective: ・目的
Play a HLS stream in an Android APP with ExoPlayer & display Google IMA advertisements.
ExoPlayerを使用してAndroidアプリでHLSストリームを再生し、Google IMA広告を表示します。

Implementation・ 実装

Step 0

HLS

If you don’t have a HLS stream, please visit the following page to find one.
HLSストリームがない場合は、以下のページをご覧ください。
https://github.com/notanewbie/LegalStream

IMA

If you don’t have IMA credentials, you can use demo tags.
IMA資格情報がない場合は、デモタグを使用できます。
https://developers.google.com/interactive-media-ads/docs/sdks/html5/tags

Step 1

Create a new project & a blank Main Activity.
新しいプロジェクトと空白のメインアクティビティを作成してください。

MainActivity.java

package jp.co.mycompany.com.exotuto;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}
AndroidManifest.xml

Enable hardware acceleration.
ハードウェアアクセラレーションを有効にしてください。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="jp.co.mycompany.com.exotuto">

    <application
        android:allowBackup="true"
        android:hardwareAccelerated="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>
Dependencies ・ソフトウェアの依存関係

buid.gradle(Module:app)

apply plugin: 'com.android.application'

android {
    compileSdkVersion 27
    defaultConfig {
        applicationId "jp.co.mycompany.com.exotuto"
        minSdkVersion 15
        targetSdkVersion 27
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:27.0.0'
    implementation 'com.android.support.constraint:constraint-layout:1.0.2'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.1'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
    compile 'com.google.android.exoplayer:exoplayer:2.6.0'
    compile 'com.google.android.exoplayer:exoplayer-hls:2.6.0'
    compile 'com.google.android.exoplayer:extension-ima:2.6.0'
}

Step 2

Create a new Basic Activity & name it PlayerActivity.
新しいBasicアクティビティを作成し、それをPlayerActivityと名づけます。

Then modify MainActivity.java as follows:
MainActivity.java を次のように変更します。

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Intent intent = new Intent(this, PlayerActivity.class);
        startActivity(intent);


    }
}

Step 3

Simple HLS implementation・ 単純なHLS実装

res/layout/content_player.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:context="jp.co.mycompany.com.exotuto.PlayerActivity"
    tools:showIn="@layout/activity_player">

    <com.google.android.exoplayer2.ui.SimpleExoPlayerView
        android:id="@+id/player_view"
        android:focusable="true"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginTop="10dp" />

</android.support.constraint.ConstraintLayout>

PLayerActivity.java

public class PlayerActivity extends AppCompatActivity {

    //Player
    private SimpleExoPlayerView simpleExoPlayerView;
    private SimpleExoPlayer player;

    //Logs
    final private String TAG = "PlayerActivity";

    //HLS
    final private String VIDEO_URL = "https://nhkworld.webcdn.stream.ne.jp/www11/nhkworld-tv/domestic/263942/live_wa_s.m3u8";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_player);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                        .setAction("Action", null).show();
            }
        });

        //ExoPlayer implementation
        //Create a default TrackSelector
        BandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
        TrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory(bandwidthMeter);
        TrackSelector trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory);

        // Create a default LoadControl
        LoadControl loadControl = new DefaultLoadControl();
        //Bis. Create a RenderFactory
        RenderersFactory renderersFactory = new DefaultRenderersFactory(this);

        //Create the player
        player = ExoPlayerFactory.newSimpleInstance(renderersFactory, trackSelector, loadControl);
        simpleExoPlayerView = new SimpleExoPlayerView(this);
        simpleExoPlayerView = (SimpleExoPlayerView) findViewById(R.id.player_view);


        //Set media controller
        simpleExoPlayerView.setUseController(true);
        simpleExoPlayerView.requestFocus();

        // Bind the player to the view.
        simpleExoPlayerView.setPlayer(player);

        // Set the media source
        Uri mp4VideoUri = Uri.parse(VIDEO_URL);

        //Measures bandwidth during playback. Can be null if not required.
        DefaultBandwidthMeter bandwidthMeterA = new DefaultBandwidthMeter();

        //Produces DataSource instances through which media data is loaded.
        DefaultDataSourceFactory dataSourceFactory = new DefaultDataSourceFactory(this, Util.getUserAgent(this, "PiwikVideoApp"), bandwidthMeterA);

        //Produces Extractor instances for parsing the media data.
        ExtractorsFactory extractorsFactory = new DefaultExtractorsFactory();

        //FOR LIVE STREAM LINK:
        MediaSource videoSource = new HlsMediaSource(mp4VideoUri, dataSourceFactory, 1, null, null);
        final MediaSource mediaSource = videoSource;


        player.prepare(videoSource);


    }

    //Android Life cycle
    @Override
    protected void onStop() {
        player.release();
        super.onStop();
        Log.v(TAG, "onStop()...");
    }

    @Override
    protected void onStart() {
        super.onStart();
        Log.v(TAG, "onStart()...");
    }

    @Override
    protected void onResume() {
        super.onResume();
        Log.v(TAG, "onResume()...");
    }

    @Override
    protected void onPause() {
        super.onPause();
        Log.v(TAG, "onPause()...");
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.v(TAG, "onDestroy()...");
        player.release();
    }



}

Please run your app in the Android emulator.
Androidエミュレータでアプリをテストしてください。

Screen Shot 2018-01-09 at 18.52.34.png

Step 4

Player listeners ・プレーヤーリスナー

In the onCreate method of PlayerActivity.java, please add the following listeners.
PlayerActivityのonCreateメソッドで使用します。次のリスナーを追加してください。

//ExoPLayer events listener
        player.addListener(new Player.EventListener() {

            @Override
            public void onTimelineChanged(Timeline timeline, Object manifest) {
                Log.v(TAG, "Listener-onTimelineChanged...");
            }

            @Override
            public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
                Log.v(TAG, "Listener-onTracksChanged...");
            }

            @Override
            public void onLoadingChanged(boolean isLoading) {
                Log.v(TAG, "Listener-onLoadingChanged...isLoading:" + isLoading);
            }

            @Override
            public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
                Log.v(TAG, "Listener-onPlayerStateChanged..." + playbackState);

                switch (playbackState) {


                    case Player.STATE_IDLE:
                        Log.v(TAG, "STATE IDLE");
                        break;

                    case Player.STATE_BUFFERING:
                        Log.v(TAG, "STATE BUFFERING");
                        break;

                    case Player.STATE_READY:
                        Log.v(TAG, "STATE READY");
                        break;

                    case Player.STATE_ENDED:
                        Log.v(TAG, "STATE ENDED");
                        break;

                    default:
                        break;
                }
            }

            @Override
            public void onRepeatModeChanged(int repeatMode) {
                Log.v(TAG, "Listener-onRepeatModeChanged...");
            }

            @Override
            public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {

            }

            @Override
            public void onPlayerError(ExoPlaybackException error) {
                Log.v(TAG, "Listener-onPlayerError...");
                player.stop();
                player.prepare(adsMediaSource);
                player.setPlayWhenReady(true);

            }

            @Override
            public void onPositionDiscontinuity(int reason) {
                Log.v(TAG, "Listener-onPositionDiscontinuity...");

            }

            @Override
            public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
                Log.v(TAG, "Listener-onPlaybackParametersChanged...");
            }

            @Override
            public void onSeekProcessed() {

            }
        });

Step 5

IMA implementation ・IMAの実装

In PlayerActivity.java, add the following class variables.
PlayerActivity.java で、次のクラス変数を追加してください。

//IMA
private ImaAdsLoader imaAdsLoader;
final private String AD_TAG_URI = "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/ad_rule_samples&ciu_szs=300x250&ad_rule=1&impl=s&gdfp_req=1&env=vp&output=vmap&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ar%3Dpreonly&cmsid=496&vid=short_onecue&correlator=";

Please update the onCreate method of PlayerActivity.java as follows.
PlayerActivity.java のonCreateメソッドを次のように更新してください。

   //player.prepare(adsMediaSource);//Remove this line.

        imaAdsLoader = new ImaAdsLoader(this, Uri.parse(AD_TAG_URI));

        AdsMediaSource.AdsListener adsListener = new AdsMediaSource.AdsListener() {
            @Override
            public void onAdLoadError(IOException error) {
                error.printStackTrace();
            }

            @Override
            public void onAdClicked() {

            }

            @Override
            public void onAdTapped() {

            }
        };
        AdsMediaSource adsMediaSource = new AdsMediaSource(
                mediaSource,
                dataSourceFactory,
                imaAdsLoader,
                simpleExoPlayerView.getOverlayFrameLayout(),
                null,
                adsListener
        );

        player.prepare(adsMediaSource);
Results 結果

It works!
できます!

Screen Shot 2018-01-09 at 19.02.17.png

Step 6

Adding IMA event listeners ・IMAイベントリスナーの追加

Please add the following code to the onCreate method of PlayerActivity.java.
PlayerActivity.javaのonCreateメソッドに次のコードを追加してください。

  //IMA event listeners
        com.google.ads.interactivemedia.v3.api.AdsLoader adsLoader = imaAdsLoader.getAdsLoader();
        adsLoader.addAdsLoadedListener(new AdsLoader.AdsLoadedListener() {
            @Override
            public void onAdsManagerLoaded(AdsManagerLoadedEvent adsManagerLoadedEvent) {

                AdsManager imaAdsManager = adsManagerLoadedEvent.getAdsManager();
                imaAdsManager.addAdEventListener(new AdEvent.AdEventListener() {
                    @Override
                    public void onAdEvent(AdEvent adEvent) {
                        Log.v("AdEvent: ", adEvent.getType().toString());
                        switch (adEvent.getType()) {
                            case LOADED:
                                break;

                            case PAUSED:
                                break;

                            case STARTED:
                                break;

                            case COMPLETED:
                                break;

                            case ALL_ADS_COMPLETED:
                                break;

                            default:
                                break;


                        /*    Full list of events. Implement what you need.

                        LOADED, TAPPED, PAUSED, LOG, CLICKED, RESUMED, SKIPPED, STARTED, MIDPOINT,
                        COMPLETED, AD_PROGRESS, ICON_TAPPED, AD_BREAK_ENDED, AD_BREAK_READY,
                        FIRST_QUARTILE, THIRD_QUARTILE, AD_BREAK_STARTED, ALL_ADS_COMPLETED,
                        CUEPOINTS_CHANGED, CONTENT_PAUSE_REQUESTED,CONTENT_RESUME_REQUESTED

                        */
                        }

                    }
                });

            }
        });

Full project code

https://github.com/mr1985/exoplayer_demo_hls

Information sources・ 情報源

https://github.com/sakurabird/Android-Example-HLS-ExoPlayer
https://developers.google.com/interactive-media-ads/docs/sdks/android/
https://github.com/google/ExoPlayer
https://google.github.io/ExoPlayer/demo-application.html

元記事はこちら

Android ExoPlayer + HLS + IMA