SNSタイムライン動画の自動再生

## はじめに
[FiNC](https://finc.com/)のAndroidエンジニア兼Bisonの南里です。
AdventCalendarってなんで師走の忙しい時期なんでしょうね？
それでは参りましょう。

まずはこちらをご覧ください

<blockquote class="twitter-video" data-lang="en"><p lang="und" dir="ltr"><a href="https://t.co/cLPXZHEonG">pic.twitter.com/cLPXZHEonG</a></p>&mdash; 南里勇気 (@neonankiti) <a href="https://twitter.com/neonankiti/status/811946011388100608">December 22, 2016</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

ラインの自動再生を録画してみました。
佐野ひなこかわいいですね。

色々なSNSでよくみる自動再生。
実際に実装している例があまり見受けられなかったので、考えてみました。

## 機能要件

1. Wifiで自動再生(モバイルで自動再生はユーザーアンライクです)
2. 自動再生はスクリーン内に入った際に行う。
3. 複数ある場合は、占有面積が大きい1つのみを再生

## 1. Wifi部分の実装
[ConnectivityManager](https://developer.android.com/reference/android/net/ConnectivityManager.html)というクラスがあります。
このクラスは、ネットワークの状態、変化に関して責務を負うクラスです

現在のネットワークがwifiであることを取得するには、以下のコードを書きます。

```java

// ネットワーク状態の取得
private static NetworkInfo getNetworkInfo(@NonNull Context context) {
    ConnectivityManager connectivityManager 
        = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
    return connectivityManager.getActiveNetworkInfo();
}


// Wifiステータス
public static boolean isWifi(@NonNull Context context) {
    return getNetworkType(context) == ConnectivityManager.TYPE_WIFI;
}
```

簡単ですね。


## 2. 自動再生はスクリーン内に入った際に行う<br>3. 複数ある場合は、占有面積が大きい1つのみを再生

方針は、RecyclerView.OnScrollListerで画面上に表示されている各コンテンツ(ViewHolder)のpositionを取得し、動画を始めるべきpositionかどうか判断します。
動画を始めるべきpositionであれば開始し、そうでなければそのコンテンツは動画を止めます。

ReyclerViewを利用する際には、そのイベントリスナーとして、`RecyclerView.OnScrollListener` を利用します。

コンテンツが画面内にあるかどうかは取得できるのですが、コンテンツが再生すべき動画であることを特定するには、それぞれのコンテンツごとの座標を取得して、計算する必要があります。

簡単にスクリーン内にあるコンテンツを特定するためにまず、以下のようなinterfaceを定義します。
そして、OnScrollListener内でinsideScreenを呼び出してあげます。


```SampleActivity.java
interface OnPositionListener {
    void insideScreen(int startPosition);
}

// positionのlisterはMapで持ちます。
private HashMap<Integer, OnCompletelyVisiblePositionListener> positionListeners = new HashMap<>();

public void setOnPositionListener(
        int position, OnPositionListener positionListener) {
    this.positionListeners.put(position, positionListener);
}
```

insideScreen内での実装では、スクリーン上の全てのコンテンツ(ViewHolder)に対して、startPositionを渡してあげます。
ViewHolderのgetAdapterPosition()と比較して、startPositionであれば、VideoView(ExoPlayerが最近の流行りですかね)をstart()するという形です。

```SampleActivity.java
private final RecyclerView.OnScrollListener listener = new RecyclerView.OnScrollListener() {
    @Override
    public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
        insideScreen(getMovieStartPosition(recyclerView));
    }
};


@Override
public void insideScreen(int startPosition) {
    for (Map.Entry<Integer, OnCompletelyVisiblePositionListener> e : onCompletelyVisiblePositionListeners.entrySet()) {
        e.getValue().insideScreen(startPosition);
    }
}
```


## 画面の占有面積を取得する部分

```SampleActivity.java
private int getMovieStartPosition(RecyclerView recyclerView) {

    // 最初のコンテンツの取得
    int firstVisibleItemPosition = ((LinearLayoutManager) recyclerView.getLayoutManager()).findFirstVisibleItemPosition();
    
    // 最後のコンテンツの取得
    int lastVisibleItemPosition = ((LinearLayoutManager) recyclerView.getLayoutManager()).findLastVisibleItemPosition();

    // コンテンツの大きさにより、画面上に表示されるコンテンツの数は異なる可能性がある。
    if (lastVisibleItemPosition - firstVisibleItemPosition == 0) {
        return firstVisibleItemPosition;
    }

    // 画面上に表示される最初と最後のアイテムのpositionの差分が1以上のとき、表示するべきコンテンツ一つを特定する必要がある。
    float ratio = 0.0f;
    int startPosition = firstVisibleItemPosition;
    
    // コンテンツ分for文を回す。
    for (int i = 0; i < lastVisibleItemPosition - firstVisibleItemPosition + 1; i++) {
    
        // positionのadapterを取得する
        RecyclerView.ViewHolder holder = recyclerView.findViewHolderForAdapterPosition(firstVisibleItemPosition + i);
        
        // findViewHolderForAdapterPosition could return null.
        if (holder == null) {
            continue;
        }
        
        // 各コンテンツのTop, Bottomの座標を取得する
        int topPosition = holder.itemView.getTop() < 0 ? 0 : holder.itemView.getTop();
        int bottomPosition = holder.itemView.getBottom() > displaymetrics.heightPixels
                ? displaymetrics.heightPixels
                : holder.itemView.getBottom();
                
        // 画面縦の比率を取得して、コンテンツごとに占有面積を比較。
        float pointedRatio = (bottomPosition - topPosition) / (float) displaymetrics.heightPixels;
        if (pointedRatio > ratio) {
            ratio = pointedRatio;
            startPosition = firstVisibleItemPosition + i;
        }
    }
    return startPosition;
}
```

上記で、タイムライン上において、動画を再生するべきpositionを特定したので、あとはViewHolder.getAdapterPosition()の値と比較して、再生するべきコンテンツなのか判断してあげてください。

## まとめ
Line, Twitter, Facebook, Vineなどで自動再生どう実装しているんだろうなー。と考えていたので、実際に実装してみるといい勉強になりました。
もっといい実装あれば、教えて下さい。