16
19

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Android その3Advent Calendar 2016

Day 11

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

Last updated at Posted at 2016-12-11

この記事は、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を作りたくなってきた!

16
19
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
19

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?