Android
Exoplayer

動画プレイヤーのシークバーの上にサムネイルを表示! 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を作りたくなってきた!