Help us understand the problem. What is going on with this article?

動画プレイヤーのシークバーの上にサムネイルを表示! on ExoPlayer

More than 1 year has passed since last update.

この記事は、Android その3 Advent Calendar 2016の11日目の記事です!

昨日は @kimukouさんのPicassoとGlideのお話でした。
明日は @granoesteさんのお話です。

動画プレイヤーを操作する、SeekBarの上にサムネイル表示の領域を設置

画像は入れてないですが、画像を表示する領域をSeekBarの位置と同期させる処理を書いて見ました。

movie.gif

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を作りたくなってきた!

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away