The two main problems faced with youtube API are:
1. when we set YoutubePlayerStlye to MINIMAL style we can't access the seekbar (Time bar).
2. Related videos pop up at the end of the video.
SOLUTION for 1:
So we have to build one custom seekbar to access the time progress and update (drag) time on seekbar as we want. But youtubePlayer doesn't allow to have a view (widget) above/top of it, when it happens youtubePlayer pauses the video.
So the solution is to draw a custom seekbar below below youtubePlayer.
We can achieve this by this layout:
youtube_player_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/black"
android:gravity="center"
android:orientation="vertical"
tools:context=".features.video.YoutubePlayerActivity">
<!-- youtube player view to play videos -->
<com.google.android.youtube.player.YouTubePlayerView
android:id="@+id/youtube_player_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="@android:color/black"
android:focusable="true" />
<!-- Full layout of seekbar, you can also use a constraintLayout-->
<LinearLayout
android:id="@+id/video_control_bottom"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="@android:color/black"
android:gravity="center"
android:orientation="horizontal">
<!-- Seekbar to display video progress -->
<SeekBar
android:id="@+id/video_seekbar"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_marginEnd="10dp"
android:layout_marginBottom="10dp"
android:layout_weight="6"
android:max="100"
android:progress="0" />
<!-- Time display -->
<TextView
android:id="@+id/play_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:gravity="center"
android:text="--:--"
android:textColor="@android:color/white" />
<!-- FullScreen button -->
<ImageButton
android:id="@+id/bt_fullscreen"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@null"
android:src="@drawable/ic_fullscreen" />
</LinearLayout>
</LinearLayout>
Portrait view:
Landscape view:
This custom seekbar also contains a fullScreen button (ImageButton) which you can add from Vector asset & handle on kotlin/Java code on change of orientation.
SOLUTION for 2:
The usual solution I found online was to set seekTime to zero when video ends.
@Override
public void onVideoEnded() {
if (null == mPlayer) return;
mPlayer.seekToMillis(0);
mPlayer.pause();
}
But this solution is not optimal because even if you use it the related videos will show for half a second sometimes which doesn't look.
So the simple solution is to never let the video to end so that no related video will be shown at the end. To achieve it we have to stop the video ~1.2 second before it end (I think ~1.2s can be sacrificed from a video but it depends on the usecase).
if (null == mPlayer) return;
if (mPlayer.getCurrentTimeMillis() >= (mPlayer.getDurationMillis() - 1200L)) {
mPlayer.seekToMillis(0);
mPlayer.pause();
}
I'm also posting the full Java code for the youtubePlayer.
YoutubePlayerActivity.java
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.WindowManager;
import android.widget.ImageButton;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;
import androidx.core.content.ContextCompat;
import com.google.android.youtube.player.YouTubeBaseActivity;
import com.google.android.youtube.player.YouTubeInitializationResult;
import com.google.android.youtube.player.YouTubePlayer;
import com.google.android.youtube.player.YouTubePlayerView;
import com.syncnicia.theguideapp.R;
public class YoutubePlayerActivity extends YouTubeBaseActivity {
private static final String TAG = "YoutubePlayerActivity";
private String videoID;
private YouTubePlayerView youTubePlayerView;
private TextView mPlayTimeTextView;
private YouTubePlayer mPlayer;
private Handler mHandler = null;
private SeekBar mSeekBar;
private ImageButton btFullScreen;
private int seekTime = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_youtube_player);
//get the video id
videoID = getIntent().getStringExtra("video_id");
youTubePlayerView = findViewById(R.id.youtube_player_view);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
btFullScreen = findViewById(R.id.bt_fullscreen);
btFullScreen.setOnClickListener(view -> {
int orientation = this.getResources().getConfiguration().orientation;
if (null != mPlayer) {
if (mPlayer.isPlaying()) {
mPlayer.pause();
}
if (orientation == Configuration.ORIENTATION_PORTRAIT) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
} else {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
mPlayer.play();
}
});
mPlayTimeTextView = (TextView) findViewById(R.id.play_time);
mSeekBar = (SeekBar) findViewById(R.id.video_seekbar);
mSeekBar.setOnSeekBarChangeListener(mVideoSeekBarChangeListener);
mHandler = new Handler();
initializeYoutubePlayer();
}
@Override
protected void onResume() {
super.onResume();
if (mPlayer != null) {
initializeYoutubePlayer();
}
}
private void initializeYoutubePlayer() {
youTubePlayerView.initialize(String.valueOf(getText(R.string.youtube_key)), new YouTubePlayer.OnInitializedListener() {
@Override
public void onInitializationSuccess(YouTubePlayer.Provider provider, YouTubePlayer youTubePlayer,
boolean wasRestored) {
if (null == youTubePlayer) return;
mPlayer = youTubePlayer;
displayCurrentTime();
//if initialization success then load the video id to youtube player
if (!wasRestored) {
if (videoID != null) {
youTubePlayer.loadVideo(videoID, seekTime);
}
}
//set the player style here: like CHROMELESS, MINIMAL, DEFAULT
youTubePlayer.setPlayerStyle(YouTubePlayer.PlayerStyle.MINIMAL);
// Add listeners to YouTubePlayer instance
youTubePlayer.setPlayerStateChangeListener(mPlayerStateChangeListener);
youTubePlayer.setPlaybackEventListener(playbackEventListener);
}
private final YouTubePlayer.PlaybackEventListener playbackEventListener = new YouTubePlayer.PlaybackEventListener() {
@Override
public void onPlaying() {
mHandler.postDelayed(runnable, 100);
displayCurrentTime();
}
@Override
public void onPaused() {
mHandler.removeCallbacks(runnable);
}
@Override
public void onStopped() {
mHandler.removeCallbacks(runnable);
}
@Override
public void onBuffering(boolean b) {
}
@Override
public void onSeekTo(int i) {
mHandler.postDelayed(runnable, 100);
}
};
@Override
public void onInitializationFailure(YouTubePlayer.Provider arg0, YouTubeInitializationResult arg1) {
//print or show error if initialization failed
Log.e(TAG, "Youtube Player View initialization failed:" + arg1);
if (arg1.toString().equals("SERVICE_VERSION_UPDATE_REQUIRED")) {
Toast.makeText(YoutubePlayerActivity.this, "Please Update your Youtube to latest version.", Toast.LENGTH_SHORT).show();
}
}
});
}
YouTubePlayer.PlayerStateChangeListener mPlayerStateChangeListener = new YouTubePlayer.PlayerStateChangeListener() {
@Override
public void onLoading() {
}
@Override
public void onLoaded(String s) {
}
@Override
public void onAdStarted() {
}
@Override
public void onVideoStarted() {
displayCurrentTime();
}
@Override
public void onVideoEnded() {
if (null == mPlayer) return;
mPlayer.seekToMillis(0);
mPlayer.pause();
}
@Override
public void onError(YouTubePlayer.ErrorReason errorReason) {
}
};
SeekBar.OnSeekBarChangeListener mVideoSeekBarChangeListener = new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
if (null == mPlayer) return;
long lengthPlayed = (mPlayer.getDurationMillis() * seekBar.getProgress()) / 100;
mPlayer.seekToMillis((int) lengthPlayed);
}
};
private void displayCurrentTime() {
//To stop video 1.2s before it ends
if (null == mPlayer) return;
if (mPlayer.getCurrentTimeMillis() >= (mPlayer.getDurationMillis() - 1200L)) {
mPlayer.seekToMillis(0);
mPlayer.pause();
}
String formattedTime = formatTime(mPlayer.getDurationMillis() - mPlayer.getCurrentTimeMillis());
mPlayTimeTextView.setText(formattedTime);
int playPercent = (int) (((float) mPlayer.getCurrentTimeMillis() / (float) mPlayer.getDurationMillis()) * 100);
// update live progress
mSeekBar.setProgress(playPercent);
}
private String formatTime(int millis) {
int seconds = millis / 1000;
int minutes = seconds / 60;
int hours = minutes / 60;
return (hours == 0 ? "" : hours + ":") + String.format("%02d:%02d", minutes % 60, seconds % 60);
}
private Runnable runnable = new Runnable() {
@Override
public void run() {
displayCurrentTime();
mHandler.postDelayed(this, 100);
}
};
@Override
public void onDestroy() {
if (mPlayer != null) {
mPlayer.release();
}
super.onDestroy();
}
@Override
protected void onPause() {
super.onPause();
if (mPlayer != null) {
seekTime = mPlayer.getCurrentTimeMillis();
}
}
@Override
protected void onStop() {
if (mPlayer != null) {
mPlayer.release();
}
super.onStop();
}
// To switch fullScreen icon when orientation changes
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
btFullScreen.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.ic_fullscreen));
} else if(newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE){
btFullScreen.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.ic_fullscreen_exit));
}
}
}
I think direct copy paste and adding drawables will be enough for it to work.
Let me know if the code is still confusing, I'll add more comments to it.
I hope it works for everyone!! because it took more than an hour for me π
.