Android ExoPlayer + HLS + IMA

#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***

```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
<?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)

```groovy

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***と名づけます。

<img width="797" alt="Screen Shot 2018-01-09 at 12.06.36.png" src="https://qiita-image-store.s3.amazonaws.com/0/229807/749dc7fc-5934-efd2-e151-24cd1379cbe2.png">

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

```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
<?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***

```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エミュレータでアプリをテストしてください。

<img width="389" alt="Screen Shot 2018-01-09 at 18.52.34.png" src="https://qiita-image-store.s3.amazonaws.com/0/229807/2feb0bf7-5aab-5acf-b133-5e7a27e42d31.png">

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

```java
//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*** で、次のクラス変数を追加してください。

```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メソッドを次のように更新してください。

```java
         //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!
できます！
<img width="405" alt="Screen Shot 2018-01-09 at 19.02.17.png" src="https://qiita-image-store.s3.amazonaws.com/0/229807/f0ba935a-217d-05bd-1d11-8a33183ee028.png">

###Step 6
####Adding IMA event listeners ・IMAイベントリスナーの追加
Please add the following code to the onCreate method of PlayerActivity.java.
PlayerActivity.javaのonCreateメソッドに次のコードを追加してください。

```java
 //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