Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
16
Help us understand the problem. What is going on with this article?
@ueno-yuhei

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

More than 3 years have 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を作りたくなってきた!

16
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  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
ueno-yuhei
水樹奈々が好き!

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
16
Help us understand the problem. What is going on with this article?