0

I use single Activity pattern in my app using Navigation component. I use YouTube Android library for playing the video. When I click full screen icon on video player the top and bottom tool bars have to be gone and the screen has to be changed on landscape mode. But after the screen has rotated the activity was recreated and video stops and starts over. The question is how to keep playing the video after the screen has rotated? I found one solution to add configChanges to the manifest file

<activity
            android:name=".ui.MainActivity"
            android:configChanges="orientation|screenSize|keyboardHidden|smallestScreenSize|screenLayout" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

This solved my problem, the activity stopped being recreated when the screen was rotated. But I do not want this behavior in all fragments, I need it only in the fragment where the video player is located.

This is my code in Fragment:

 private fun fullScreenListener() {

        val decorView = activity?.window?.decorView?.let {
            val screenListener = object : YouTubePlayerFullScreenListener {
                override fun onYouTubePlayerEnterFullScreen() {
                    binding.youtubePlayer.enterFullScreen()
                    hideSystemUi(it)
                }

                override fun onYouTubePlayerExitFullScreen() {
                    showSystemUi(it)
                }

            }
            binding.youtubePlayer.addFullScreenListener(screenListener)
        }

    }

    private fun hideSystemUi(view: View) {
        activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE

        MainActivity.hideBottomNavBar()

        WindowCompat.setDecorFitsSystemWindows(requireActivity().window, false)
        WindowInsetsControllerCompat(requireActivity().window,view).let { controller ->
            controller.hide(WindowInsetsCompat.Type.systemBars())
            controller.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
        }
    }
    
    private fun showSystemUi(view: View) {

        activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT

        MainActivity.showBottomNavBar()

        WindowCompat.setDecorFitsSystemWindows(requireActivity().window, true)
        WindowInsetsControllerCompat(requireActivity().window, view).show(WindowInsetsCompat.Type.systemBars())

    }
Artem
  • 93
  • 1
  • 6

3 Answers3

1

You really do not want to do this. The problem isn't just the restart on rotation, it's that there's at LEAST a dozen situations that can cause an Activity restart, and you can't block some of them. On Android this is really just something you need to live with, and learn how to code to make it cleanly restart.

And no, you can't do configChanges at runtime or only for some fragments. It works on an Activity level.

Instead, you should ask a different question- tell use what isn't working when you rotate, and ask how to fix that with restart.

Gabe Sechan
  • 90,003
  • 9
  • 87
  • 127
1

Sorry i am not a kotlin developer, but this solution fixed mine. But before i post the codes, let me explain it to you, although it's not the most reliable but a better option.

Note: If you add these lines to your manifest, there are a lot of android configuration changes that will not be handled by below lines.

Take for instance you added this line to your manifest file.

 android:configChanges="orientation|screenSize|keyboardHidden|smallestScreenSize|screenLayout">

Now, i'll prove to you that adding above line is not an option at all:

  • Firstly, assuming this line is still in your manifest file, then if your app targets Api level 29 and above, toggle the android system ui dark mode which is located in Settings > Display & Brightness > Dark theme then return back to your app and you'll notice that your activity has been recreated and the video restarts. Now, to avoid that, then you'll need to add Uimode to the above line of code.

    android:configChanges="uimode|orientation|screenSize|keyboardHidden|smallestScreenSize|screenLayout" >
    

(Notice the difference between it and the first code)

Now that you've added uimode to configChanges, the particular activity won't be able to detect changes when the android system ui dark mode switch is toggled. But it's still not the best because it will cause the following:

  • A bad user experience whereby if a user switches theme but theme changes doesn't reflect in your app.
  • Let's assume that you have an Alert dialog that's still showing and you rotate your screen, the width tends to overlap the screen due to the smallestScreenSize | screenLayout attribute.
  • Let's assume that you're onMultiWindowChanged, it'll cause bad user experience too wherby the activity will want to resize and recreate the screen ui layout in order to adjust to the multi window mode but you'll end up seeing overlaps.

Anyways, there are so many configuration changes that will cause activity to restart and instead of adding this line and changing the configChanges attribute everytime just make use of the:

  • onSavedInsatnceState and onRestoreInstanceState attributes or
  • Make use of android new method of saving ui state which is viewModel and savedStateHandle

Now, if you want use method 1, you need to understand Android lifecycle architecture component first then use the onSavedInsatnceState to save and use the onRestoreInsatnceState to restore the ui states. But according to https://developer.android.com/reference/android/app/Activity

Starting with Honeycomb, an application is not in the killable state until its onStop() has returned. This impacts when onSaveInstanceState(android.os.Bundle) may be called (it may be safely called after onPause()) and allows an application to safely wait until onStop() to save persistent state.

Declare this as global variable

private final String KEY_YOUTUBE_VIDEO_LENGTH_STATE = "youtube_length_state";

Override onSavedInsatnceState method and add below codes.

@Override
protected void onSaveInstanceState(Bundle savedInstanceState) {
 // save YouTube video length state
    long videoStateLong = binding.youtubePlayer.getVideoLength();
    savedInstanceState.putLong(KEY_YOUTUBE_VIDEO_LENGTH_STATE, videoStateLong);

//Call below line to save bundle

   super.onSaveInstanceState(savedInstanceState);
}

Then override onRestoreInstanceState and add below lines.

@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);

// Retrieve video state and it's length.
if(savedInstanceState != null) {
    binding.youtubePlayer.setVideoLength = savedInstanceState.getLong(KEY_YOUTUBE_VIDEO_LENGTH_STATE);
}

Finally, incase onRestoreInstanceState is not called then override onResume method and add below lines of codes.

@Override
protected void onResume() {
super.onResume();
Bundle savedInstanceState = new Bundle();
 if (savedInstanceState != null) {
    binding.youtubePlayer.getVideoLength.onRestoreInstanceState(KEY_YOUTUBE_VIDEO_LENGTH_STATE);
}

} Now, in the onCreate method, add below lines

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    if (savedInstanceState==null){
        playVideoFromBeginning(); // No video length is saved yet, play video from beginning 
    }else{
        restoreVideoPreviousLength(savedInstanceState); // Restore video length found in the Bundle and pass savedInstanceState as an argument 
    }
}

public void restoreVideoPreviousLength(Bundle savedInstanceState) {

 binding.youtubePlayer.setVideoLength = savedInstanceState.getLong(KEY_YOUTUBE_VIDEO_LENGTH_STATE);
}

Note:

  • codes in onCreate method will only work for screen rotations but those in onResume will work for uimode change etc.
  • onSavedInsatnceState and onRestoreInstanceState should NEVER be used to store large datasets like fetching Recyclerview items. ViewModel should be used in cases like this instead.

Now, if you want to use the second method which is viewModel method:

Note: ViewModel's only responsibility is to manage the data for the UI. It should never access your view hierarchy or hold a reference back to the Activity or the Fragment.

Now you can learn more from here https://www.geeksforgeeks.org/viewmodel-with-savedstate-in-android/

Remember, i'm not a kotlin developer

chisom emmanuel
  • 166
  • 2
  • 6
0

Based on your new answer- I'm surprised your video view doesn't support this without work. However, if you implement onSaveInstanceState to save the seek time of the video and onRestoreInstanceState to seek to that time, it should work with at most a brief hiccup as it reads in the video.

Gabe Sechan
  • 90,003
  • 9
  • 87
  • 127