この記事は、Android その3 Advent Calendar 2016の11日目の記事です!
昨日は @kimukouさんのPicassoとGlideのお話でした。
明日は @granoesteさんのお話です。
動画プレイヤーを操作する、SeekBarの上にサムネイル表示の領域を設置
画像は入れてないですが、画像を表示する領域をSeekBarの位置と同期させる処理を書いて見ました。
setThumbnailXというメソッドでImageViewのX座標を動かしています。
Seek中は、ProgressBarをVISIBLEにして読み込み感を出してます。
座標を計算して、SeekBarの横幅以上にImageViewの位置がはみ出なければ移動、
はみ出る場合は、ImageViewを一番左か一番右に座標をセットしています。
※ 動画プレイヤーは、最近リリースされたExoPlayerのrelease-v2ブランチを使用しています。
ソース
public class ExoPlayerActivity extends FragmentActivity implements TextureView.SurfaceTextureListener {
    private ExoPlayerActivityBinding binding;
    private SimpleExoPlayer simpleExoPlayer;
    // Apple sample HLS
    private static final String videoUrl = "http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8";
    private Timer playerTimer;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = DataBindingUtil.setContentView(this, R.layout.exo_player_activity);
        // TextureViewのコールバックをセット
        binding.textureView.setSurfaceTextureListener(this);
        // ステータスバー、ナビゲーションバーを非表示に
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
        View decor = getWindow().getDecorView();
        if (Build.VERSION.SDK_INT > 18) {
            decor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                    | View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
        } else if (Build.VERSION.SDK_INT > 15) {
            decor.setSystemUiVisibility(
                    View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN);
        } else if (Build.VERSION.SDK_INT > 13) {
            decor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
        } else {
            decor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
        }
        // Playerの領域を16 : 9 へ
        binding.raitoFrameLayout.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                if (binding.raitoFrameLayout.getWidth() > 0 && binding.raitoFrameLayout.getHeight() > 0) {
                    int width = binding.raitoFrameLayout.getWidth();
                    ViewGroup.LayoutParams params = binding.raitoFrameLayout.getLayoutParams();
                    params.height = width / 16 * 9;
                    binding.raitoFrameLayout.setLayoutParams(params);
                    if (Build.VERSION.SDK_INT >= 16) {
                        binding.raitoFrameLayout.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                    } else {
                        binding.raitoFrameLayout.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                    }
                }
            }
        });
        // 読み込み中のProgressbar表示
        binding.loadingProgress.setVisibility(View.VISIBLE);
        // SeekBarのイベントセット
        binding.controlSeek.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                if (progress == 0 || seekBar.getMax() == 0) return;
                if (fromUser) {
                    // ユーザーがSeekBarをいじってシークしたらPlayerをその秒数へ
                    simpleExoPlayer.seekTo(progress * 1000);
                    setThumbnailX(progress, seekBar.getMax());
                }
            }
            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {
                // ユーザーがシーク開始
                binding.loadingProgress.setVisibility(View.VISIBLE);
            }
            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {
                // ユーザーがシーク終了
                binding.loadingProgress.setVisibility(View.INVISIBLE);
            }
        });
    }
    // 秒数とSeekBarの位置を更新するPlayer監視Timer
    private void setTimer() {
        stopTimer();
        playerTimer = new Timer();
        playerTimer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                final int durationSecond = (int) (simpleExoPlayer.getDuration() / 1000);
                final int currentSecond = (int) (simpleExoPlayer.getCurrentPosition() / 1000);
                binding.time.post(new Runnable() {
                    @Override
                    public void run() {
                        binding.time.setText(currentSecond + "/" + durationSecond);
                        binding.controlSeek.setMax(durationSecond); // SeekBarのMax値を動画の秒数に
                        binding.controlSeek.setProgress(currentSecond); // SeekBarのつまみを現在の秒数の箇所へ移動
                        setThumbnailX(currentSecond, durationSecond);
                    }
                });
            }
        }, 1000, 1000);
    }
    // SeekBar上の画像の位置を移動 Viewの大きさから位置を計算
    private void setThumbnailX(int currentPosition, int durationPosition) {
        int seekWidth = binding.controlSeek.getWidth();
        int imageWidth = binding.videoImage.getWidth();
        double videoImageX = (double) seekWidth * currentPosition / durationPosition;
        if (videoImageX > seekWidth - imageWidth / 2) {
            binding.videoImage.setX(seekWidth - imageWidth);
        } else if (videoImageX > imageWidth / 2) {
            binding.videoImage.setX((int) videoImageX - imageWidth / 2);
        } else {
            binding.videoImage.setX(0);
        }
    }
    // Player監視Timerを削除
    private void stopTimer() {
        if (playerTimer != null) {
            playerTimer.cancel();
            playerTimer.purge();
            playerTimer = null;
        }
    }
    @Override
    protected void onResume() {
        super.onResume();
        if (simpleExoPlayer != null) {
            setTimer();
            simpleExoPlayer.setPlayWhenReady(true);
        }
    }
    @Override
    protected void onPause() {
        super.onPause();
        stopTimer();
        if (simpleExoPlayer != null) {
            simpleExoPlayer.setPlayWhenReady(false);
        }
    }
    // ExoPlayer2 TextureViewの準備ができたらExoPlayerにTextureViewをセットして再生開始
    @Override
    public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int i, int i1) {
        Handler mainHandler = new Handler();
        BandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
        TrackSelection.Factory videoTrackSelectionFactory = new AdaptiveVideoTrackSelection.Factory(bandwidthMeter);
        TrackSelector trackSelector = new DefaultTrackSelector(mainHandler, videoTrackSelectionFactory);
        LoadControl loadControl = new DefaultLoadControl();
        simpleExoPlayer = ExoPlayerFactory.newSimpleInstance(getApplicationContext(), trackSelector, loadControl);
        simpleExoPlayer.setVideoTextureView(binding.textureView);
        DefaultBandwidthMeter defaultBandwidthMeter = new DefaultBandwidthMeter();
        DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(this,
                Util.getUserAgent(this, new WebView(getApplicationContext()).getSettings().getUserAgentString()), defaultBandwidthMeter);
        HlsMediaSource hlsMediaSource = new HlsMediaSource(Uri.parse(videoUrl), dataSourceFactory, mainHandler, null);
        simpleExoPlayer.prepare(hlsMediaSource);
        simpleExoPlayer.setVideoListener(new SimpleExoPlayer.VideoListener() {
            @Override
            public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) {
            }
            @Override
            public void onRenderedFirstFrame() {
                // 再生開始
                binding.loadingProgress.setVisibility(View.INVISIBLE);
                setTimer();
            }
            @Override
            public void onVideoTracksDisabled() {
            }
        });
        simpleExoPlayer.setPlayWhenReady(true);
    }
    @Override
    public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int i, int i1) {
    }
    @Override
    public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
        return false;
    }
    @Override
    public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
    }
    // ExoPlayer2
}
XML
<layout>
    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#000">
        <RelativeLayout xmlns:tools="http://schemas.android.com/tools"
            android:id="@+id/raito_frame_layout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_gravity="center"
            tools:context=".activity.ExoPlayerActivity">
            <TextureView
                android:id="@+id/texture_view"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_gravity="center" />
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_alignParentBottom="true"
                android:layout_margin="8dp"
                android:orientation="vertical">
                <TextView
                    android:id="@+id/time"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_gravity="right|bottom"
                    android:layout_margin="8dp"
                    android:textColor="#FFF"
                    android:textSize="16sp" />
                <ImageView
                    android:id="@+id/video_image"
                    android:layout_width="64dp"
                    android:layout_height="36dp"
                    android:layout_margin="8dp"
                    android:background="#FFF" />
                <SeekBar
                    android:id="@+id/control_seek"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_gravity="bottom" />
            </LinearLayout>
            <ProgressBar
                android:id="@+id/loading_progress"
                style="?android:attr/progressBarStyle"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerInParent="true" />
        </RelativeLayout>
    </FrameLayout>
</layout>
色々なPlayerControllerを作りたくなってきた!
