What is the best way of using Youtube API in Android?
Asked Answered
C

1

1

I want to implement YouTube API in my Android app with some restrictions.

  1. The Play/Pause button should be visible. (Both DEFAULT and MINIMAL style support this)

  2. The time bar should be visible and operable. (In case of MINIMAL style, the time bar is only visible but clicking on it does not have any effect)

  3. The user should not be able to share the video or open the YouTube app directly. (In this case, DEFAULT is NOT an option)

The MINIMAL style would have worked perfectly, but the time bar does not work. I have considered using Custom Seekbar but it is not visible when the video is in full screen mode. Any suggestion?

Chicory answered 6/5, 2021 at 8:22 Comment(3)
You can make the seekBar visible in fullScreen by putting it inside LinearLayout with the YouTube player and handling its lifecycle during fullScreen orientation. – Monetta
It does not allow any other widget or layout on top of it... Also it requires enough screen space. – Chicory
Yes, exactly I also faced this issue! I think I can help... – Monetta
M
0

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:

Portrait view of youtube player with custom seekbar

Landscape view:

Landscape view of youtube player with custom seekbar

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 πŸ˜….

Monetta answered 29/10, 2021 at 6:46 Comment(3)
Won't the landscape view create any problem as it is also mentioned that the youtubeview should have enough space to accomodate the video? – Chicory
No, It won't, we just have to make sure that we don't interrupt the youtube view by creating any view above it. The code is fully tested with orientation changes & it's not interrupting the continuity of playing video. – Monetta
Yup, if it works don't forget to upvote. πŸ˜… – Monetta

© 2022 - 2024 β€” McMap. All rights reserved.