My aim is to show several video at one Activity at the same time using ExoPlayer 2. (I got hls source for each video). I'm succesfully play one video. So I decided to make implementation of the Player inside Fragment and create new Fragment for each hls sources to put them inside Activity. But only one Fragment succesfully playing video, other Fragments looks like black square without any content. How to resolve it?
I'm using ExoPlayer 2.7.2 .
My Activity code
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
Bundle bundle1 = new Bundle();
bundle1.putString(SmallPlayerfragment.VIDEO_KEY, SmallPlayerfragment.Source1);
Bundle bundle2 = new Bundle();
bundle2.putString(SmallPlayerfragment.VIDEO_KEY, SmallPlayerfragment.Source2);
Fragment fragment1 = new SmallPlayerfragment();
fragment1.setArguments(bundle1);
Fragment fragment2 = new SmallPlayerfragment();
fragment2.setArguments(bundle2);
if (getSupportFragmentManager().findFragmentByTag(SmallPlayerfragment.TAG1) == null
| getSupportFragmentManager().findFragmentByTag(SmallPlayerfragment.TAG2) == null)
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.test_container, fragment2, SmallPlayerfragment.TAG2)
.replace(R.id.bottom_test_container, fragment1, SmallPlayerfragment.TAG1)
.commit();
}
My Fragment code
public class SmallPlayerfragment extends Fragment {
String mVideoURL;
public final static String VIDEO_KEY = "videoKey";
public final static String Source2 = "source2";
public final static String Source1 = "source1";
public final static String TAG1 = "fragment_1";
public final static String TAG2 = "fragment_2";
PlayerView mPlayerView;
SimpleExoPlayer mPlayer;
public SmallPlayerfragment() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_small_player, container, false);
}
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mPlayerView = getActivity().findViewById(R.id.small_player);
if (getArguments() != null) {
mVideoURL = getArguments().getString(VIDEO_KEY);
} else {
mVideoURL = Source1;
}
}
#Override
public void onStart() {
super.onStart();
Log.d("test", "onStart Fragment");
if (Util.SDK_INT > 23) {
initializePlayer();
}
}
#Override
public void onResume() {
super.onResume();
Log.d("test", "onResume Fragment");
// hideSystemUi();
if ((Util.SDK_INT <= 23 || mPlayer == null)) {
initializePlayer();
}
}
#Override
public void onPause() {
Log.d("test", "onPause Fragment");
super.onPause();
if (Util.SDK_INT <= 23) {
releasePlayer();
}
}
#Override
public void onStop() {
Log.d("test", "onStop Fragment");
super.onStop();
if (Util.SDK_INT > 23) {
releasePlayer();
}
}
#SuppressLint("InlinedApi")
private void hideSystemUi() {
mPlayerView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE
| View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
}
private void initializePlayer() {
mPlayer = ExoPlayerFactory.newSimpleInstance(
new DefaultRenderersFactory(getContext()),
new DefaultTrackSelector(), new DefaultLoadControl());
mPlayerView.setPlayer(mPlayer);
mPlayer.seekTo(0);
Uri uri = Uri.parse(mVideoURL);
MediaSource mediaSource = buildMediaSource(uri);
mPlayer.prepare(mediaSource, true, false);
mPlayer.setPlayWhenReady(true);
}
private MediaSource buildMediaSource(Uri uri) {
// Measures bandwidth during playback. Can be null if not required.
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
// Produces DataSource instances through which media data is loaded.
DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(getContext(),
Util.getUserAgent(getContext(), "yourApplicationName"), bandwidthMeter);
// This is the MediaSource representing the media to be played.
MediaSource videoSource = new HlsMediaSource.Factory(dataSourceFactory)
.createMediaSource(uri);
// Prepare the player with the source.
return videoSource;
}
private void releasePlayer() {
if (mPlayer != null) {
mPlayer.release();
mPlayer = null;
}
}
I think its just a focus issue. The fragment needs to be in focus to play video.
Also in your Exoplayer fragment drop a little if to check for focus before playing the video.
The fragment in focus should only play the video.
Related
I'm trying to load a VAST ad using Exoplayer IMA extension (using tutorials 1, 2). I want to keep ad and content progress, so if app goes background or screen rotates, user continues from the point he/she was watching. You can see my code here and main codes below for convenience. (Duo to some limitations, I'm stuck with version 2.9.6 of Exoplayer library)
public class MainActivity extends AppCompatActivity {
private PlayerView playerView;
private SimpleExoPlayer player;
private static final String KEY_WINDOW = "window";
private int currentWindow;
private static final String KEY_POSITION = "position";
private long playbackPosition;
private static final String KEY_AUTO_PLAY = "auto_play";
private boolean playWhenReady;
private ImaAdsLoader adsLoader;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
playerView = findViewById(R.id.exo_video_view);
if (adsLoader == null)
adsLoader = new ImaAdsLoader(this, Uri.parse(getString(R.string.ad_tag_url)));
if (savedInstanceState != null) {
currentWindow = savedInstanceState.getInt(KEY_WINDOW);
playbackPosition = savedInstanceState.getLong(KEY_POSITION);
playWhenReady = savedInstanceState.getBoolean(KEY_AUTO_PLAY, true);
}
}
private MediaSource buildMediaSource(Uri uri, DataSource.Factory dataSourceFactory) {
return new ExtractorMediaSource.Factory(dataSourceFactory).createMediaSource(uri);
}
private MediaSource buildAdMediaSource(MediaSource contentMediaSource, DataSource.Factory dataSourceFactory){
// Create the AdsMediaSource using the AdsLoader and the MediaSource.
return new AdsMediaSource(contentMediaSource, dataSourceFactory, adsLoader, playerView);
}
#Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
}
private void initializePlayer() {
player = ExoPlayerFactory.newSimpleInstance(this);
playerView.setPlayer(player);
if (adsLoader != null)
adsLoader.setPlayer(player);
Uri contentUri = Uri.parse(getString(R.string.media_url_mp4));
DataSource.Factory dataSourceFactory =
new DefaultDataSourceFactory(this, "exoplayer-codelab");
MediaSource contentMediaSource = buildMediaSource(contentUri, dataSourceFactory);
MediaSource adMediaSource = buildAdMediaSource(contentMediaSource, dataSourceFactory);
player.setPlayWhenReady(playWhenReady);
player.seekTo(currentWindow, playbackPosition);
player.prepare(adMediaSource, false, false);
}
#Override
public void onStart() {
super.onStart();
if (Util.SDK_INT >= 24) {
initializePlayer();
if (playerView != null) {
playerView.onResume();
}
}
}
#Override
public void onResume() {
super.onResume();
hideSystemUi();
if ((Util.SDK_INT < 24 || player == null)) {
initializePlayer();
if (playerView != null) {
playerView.onResume();
}
}
}
private void hideSystemUi() {
playerView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE
| View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
}
#Override
public void onPause() {
super.onPause();
if (Util.SDK_INT < 24) {
if (playerView != null) {
playerView.onPause();
}
releasePlayer();
}
}
#Override
public void onStop() {
super.onStop();
if (Util.SDK_INT >= 24) {
if (playerView != null) {
playerView.onPause();
}
releasePlayer();
}
}
#Override
protected void onDestroy() {
super.onDestroy();
releaseAdsLoader();
}
private void releasePlayer() {
if (player != null) {
updateStartPosition();
player.release();
player = null;
}
if (adsLoader != null) {
adsLoader.setPlayer(null);
}
}
private void releaseAdsLoader() {
if (adsLoader != null) {
adsLoader.release();
adsLoader = null;
if (playerView.getOverlayFrameLayout() != null)
playerView.getOverlayFrameLayout().removeAllViews();
}
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
updateStartPosition();
outState.putBoolean(KEY_AUTO_PLAY, playWhenReady);
outState.putInt(KEY_WINDOW, currentWindow);
outState.putLong(KEY_POSITION, playbackPosition);
}
private void updateStartPosition() {
if (player != null) {
playWhenReady = player.getPlayWhenReady();
currentWindow = player.getCurrentWindowIndex();
playbackPosition = Math.max(0, player.getContentPosition());
}
}
}
Problem is, when screen rotates, activity fields like ImaAdsLoader becomes null and ad and content video play from start. I've saved player progress and can successfully restore it's position but that's not case for ImaAdsLoader since I couldn't find a way to restore its state. Am I doing anything wrong? How Ad progress state should be saved and restored?
A common pattern with video players is to prevent your activity from being recreated with orientation changes so you should add these flags to your manifest.
android:configChanges="keyboardHidden|orientation|screenSize"
This would also prevent the surface view that shows your video from being destroyed with orientation changes too.
The README.md of the Exoplayer IMA extension is quite clear about this:
You need to persist a reference to the ImaAdsLoader. When recreating the view pass that reference back when AdsLoaderProvider.getAdsLoader(MediaItem.AdsConfiguration adsConfiguration) is called.
Additionally, persist the player position when the view gets destroyed by storing the value of player.getContentPosition(). After recreation seek that position before preparing the new player instance.
You can find an example in the Exoplayer's demo app's PlayerActivity.java.
I want implement next and previous buttons below the ExoPlayer. I'm basing my code on this thread:
Implementing next button in audio player android
and the links in these ones:
How to add Listener for next , previous , rewind and forward in Exoplayer
How to add next song and previous song play button in audio player using android studio2.3?
The Exoplayer is in a fragment and opens on the right side of the screen on a tablet screen and in a separate activity on a mobile screen when the user clicks on one of the recipe steps. Data to the StepsFragment(implements an onClickListener) is sent via parcelable from the parent activity(code below).
Next and previous buttons are only implemented on the mobile screen(code works fine). I'm passing the arraylist and position from the parent activity in the onClick method and retrieving them in the fragment. The code works fine w/o the buttons, and both the list and the position are sent via parcelable to the VideoFragment only to implement the buttons. I decided to put the buttons in the Video Fragment.
Nothing happens when clicking on either the next or previous button. Code flow is indicated in the comments above the code in the onClickListener. I've tried to debug but no errors are displayed. Is this the right way to pass the arraylist? Although it's not shown in the debug and the log cat, I think it might be null. Can someone please have a look? Thank you in advance.
VideoFragment(contains Exoplayer code):
public class VideoFragment extends Fragment
{
// Tag for logging
private static final String TAG = VideoFragment.class.getSimpleName();
/**
* Mandatory empty constructor for the fragment manager to instantiate the fragment
*/
public VideoFragment()
{
}
ArrayList<Steps> stepsArrayList;
Steps stepClicked;
Recipes recipes;
SimpleExoPlayer mExoPlayer;
#BindView(R.id.playerView)
SimpleExoPlayerView mPlayerView;
#BindView(R.id.thumbnail_url)
ImageView thumbnailUrlImage;
public int stepPosition;
private long mPlayerPosition;
String videoUrl;
Uri videoUrl_Parse;
Uri thumbnailUrl_Parse;
String thumbnailUrl;
#BindView(R.id.previous_button)
Button previousButton;
#BindView(R.id.next_button)
Button nextButton;
#BindView(R.id.step_long_description)
TextView stepLongDescription;
String stepLongDescriptionUrl;
boolean mTwoPane;
private static final String KEY_POSITION = "position";
public static final String STEPS_LIST_INDEX = "list_index";
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
//Inflate the Steps fragment layout
View rootView = inflater.inflate(R.layout.fragment_video, container, false);
// Bind the views
ButterKnife.bind(this, rootView);
Bundle bundle = this.getArguments();
if (bundle != null)
{
//Track whether to display a two-pane or single-pane UI
stepClicked = getArguments().getParcelable("Steps");
if (stepClicked != null)
{
mTwoPane = getArguments().getBoolean("TwoPane");
stepPosition = getArguments().getInt("StepPosition");
stepsArrayList = getArguments().getParcelableArrayList("StepsArrayList");
stepsArrayList = new ArrayList<>();
videoUrl = stepClicked.getVideoUrl();
Log.i("VideoUrl: ", stepClicked.getVideoUrl());
videoUrl_Parse = Uri.parse(videoUrl);
thumbnailUrl = stepClicked.getThumbnailUrl();
thumbnailUrl_Parse = Uri.parse(thumbnailUrl);
stepLongDescriptionUrl = stepClicked.getStepLongDescription();
Log.i("Step: ", stepClicked.getStepLongDescription());
stepLongDescription.setText(stepLongDescriptionUrl);
if (thumbnailUrl != null)
{
Picasso.with(getContext())
.load(thumbnailUrl_Parse)
.into(thumbnailUrlImage);
}
}
if (mTwoPane)
{
previousButton.setVisibility(View.INVISIBLE);
nextButton.setVisibility(View.INVISIBLE);
} else
{
previousButton.setVisibility(View.VISIBLE);
nextButton.setVisibility(View.VISIBLE);
//https://stackoverflow.com/questions/45253477/implementing-next-button-in-audio-player-android
nextButton.setOnClickListener(new View.OnClickListener()
{
#Override
public void onClick(View v)
{
if (stepPosition < stepsArrayList.size() - 1)
{
//Add or subtract the position in 1
stepClicked= stepsArrayList.get(stepPosition);
stepPosition++;
//Using the position, get the current step from the steps list
stepClicked= stepsArrayList.get(stepPosition);
//Extract the video uri from the current step
videoUrl = stepClicked.getVideoUrl();
Log.d("VideoUrlNext: ", stepClicked.getVideoUrl());
videoUrl_Parse = Uri.parse(videoUrl);
//Call initializePlayer() by passing the new video uri
initializePlayer(videoUrl_Parse);
}
}
});
previousButton.setOnClickListener(new View.OnClickListener()
{
#Override
public void onClick(View v)
{
if (stepPosition> 0)
{
stepPosition--;
//Using the position, get the current step from the steps list
stepClicked= stepsArrayList.get(stepPosition);
//Extract the video uri from the current step
videoUrl = stepClicked.getVideoUrl();
videoUrl_Parse = Uri.parse(videoUrl);
//Call initializePlayer() by passing the new video uri
initializePlayer(videoUrl_Parse);
}
}
});
}
}
if (savedInstanceState != null)
{
stepsArrayList = savedInstanceState.getParcelableArrayList(STEPS_LIST_INDEX);
mPlayerPosition = savedInstanceState.getLong(KEY_POSITION);
}
// Return the root view
return rootView;
}
//ExoPlayer code based on: https://codelabs.developers.google.com/codelabs/exoplayer-intro/#2
public void initializePlayer(Uri videoUrl)
{
if (mExoPlayer == null)
{
TrackSelector trackSelector = new DefaultTrackSelector();
LoadControl loadControl = new DefaultLoadControl();
mExoPlayer = ExoPlayerFactory.newSimpleInstance(getActivity(), trackSelector, loadControl);
mPlayerView.setPlayer((SimpleExoPlayer) mExoPlayer);
String userAgent = Util.getUserAgent(getContext(), "Baking App");
MediaSource mediaSource = new ExtractorMediaSource(videoUrl,
new DefaultDataSourceFactory(getContext(), userAgent),
new DefaultExtractorsFactory(), null, null);
mExoPlayer.prepare(mediaSource);
if (mPlayerPosition != C.TIME_UNSET)
{
mExoPlayer.seekTo(mPlayerPosition);
}
mExoPlayer.setPlayWhenReady(true);
}
}
#Override
public void onStart()
{
super.onStart();
if (Util.SDK_INT > 23 || mExoPlayer == null)
{
initializePlayer(videoUrl_Parse);
}
}
#Override
public void onPause()
{
super.onPause();
if (mExoPlayer != null)
{
mPlayerPosition = mExoPlayer.getCurrentPosition();
}
if (Util.SDK_INT <= 23)
{
releasePlayer();
}
}
#Override
public void onResume()
{
super.onResume();
if ((Util.SDK_INT <= 23 || mExoPlayer == null))
{
mPlayerPosition = mExoPlayer.getCurrentPosition();
}
}
#Override
public void onStop()
{
super.onStop();
if (Util.SDK_INT > 23 || mExoPlayer != null)
{
mExoPlayer.getCurrentPosition();
}
releasePlayer();
}
/**
* Release ExoPlayer.
*/
private void releasePlayer()
{
if (mExoPlayer != null)
{
mPlayerPosition = mExoPlayer.getCurrentPosition();
mExoPlayer.release();
mExoPlayer = null;
}
}
#Override
public void onSaveInstanceState(Bundle outState)
{
super.onSaveInstanceState(outState);
//Save the fragment's state here
outState.putParcelableArrayList(STEPS_LIST_INDEX, stepsArrayList);
outState.putLong(KEY_POSITION, mPlayerPosition);
super.onSaveInstanceState(outState);
}
}
Parent Activity:
public class IngredientStepsActivity extends AppCompatActivity implements StepsListFragment.OnStepClickListener
{
private static final String TAG = IngredientStepsActivity.class.getSimpleName();
private Context context;
Recipes recipes;
// Track whether to display a two-pane or single-pane UI
public boolean mTwoPane;
public int stepPosition;
ArrayList<Steps> stepsArrayList;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_ingredientsteps);
// Determine if you're creating a two-pane or single-pane display
if (savedInstanceState == null)
{
if (getIntent() != null && getIntent().getExtras() != null)
{
recipes = getIntent().getExtras().getParcelable("Recipes");
if(findViewById(R.id.tablet_detail_layout) != null)
{
// This LinearLayout will only initially exist in the two-pane tablet case
mTwoPane = true;
// Only create new fragments when there is no previously saved state
/*
Add the fragment to its container using a FragmentManager and a Transaction
Send the ingredients array list in Parcelable to the Ingredients Fragment
*/
FragmentManager fragmentManager = getSupportFragmentManager();
Bundle ingredientsBundle = new Bundle();
ingredientsBundle.putParcelable("Recipes", recipes);
//Pass Over the bundle to the Ingredients Fragment
IngredientsFragment ingredientsFragment = new IngredientsFragment();
ingredientsFragment.setArguments(ingredientsBundle);
fragmentManager.beginTransaction().replace(R.id.ingredients_fragment_container, ingredientsFragment).commit();
//Pack Data in a bundle call the bundle "stepsBundle" to differentiate it from the "ingredientsBundle"
Bundle stepsBundle = new Bundle();
stepsBundle.putParcelable("Recipes", recipes);
//Pass Over the bundle to the Steps Fragment
StepsListFragment stepsListFragment = new StepsListFragment();
stepsListFragment.setArguments(stepsBundle);
fragmentManager.beginTransaction().replace(R.id.steps_fragment_container, stepsListFragment).commit();
}
else
{
// We're in single-pane mode and displaying fragments on a phone in separate activities
mTwoPane = false;
FragmentManager fragmentManager = getSupportFragmentManager();
Bundle ingredientsBundle = new Bundle();
ingredientsBundle.putParcelable("Recipes", recipes);
//Pass Over the bundle to the Ingredients Fragment
IngredientsFragment ingredientsFragment = new IngredientsFragment();
ingredientsFragment.setArguments(ingredientsBundle);
fragmentManager.beginTransaction().replace(R.id.ingredients_fragment_container, ingredientsFragment).commit();
//Pack Data in a bundle call the bundle "stepsBundle" to differentiate it from the "ingredientsBundle"
Bundle stepsBundle = new Bundle();
stepsBundle.putParcelable("Recipes", recipes);
//Pass Over the bundle to the Steps Fragment
StepsListFragment stepsListFragment = new StepsListFragment();
stepsListFragment.setArguments(stepsBundle);
fragmentManager.beginTransaction().replace(R.id.steps_fragment_container, stepsListFragment).commit();
}
}
}}
#Override
public void onClick(Steps stepClicked)
{
if (mTwoPane)
{
Bundle stepsVideoBundle = new Bundle();
stepsVideoBundle.putParcelable("Steps", stepClicked);
stepsVideoBundle.putBoolean("TwoPane", mTwoPane);
stepsVideoBundle.putInt("StepPosition", stepPosition);
stepsVideoBundle.putParcelableArrayList("StepsArrayList", stepsArrayList);
VideoFragment videoFragment = new VideoFragment();
videoFragment.setArguments(stepsVideoBundle);
getSupportFragmentManager().beginTransaction().replace(R.id.video_fragment_container, videoFragment).commit();
}
else
{
Log.i("Step: ", stepClicked.getStepShortDescription());
Intent intent = new Intent(IngredientStepsActivity.this, VideoPhoneActivity.class);
intent.putExtra("Steps", stepClicked);
startActivity(intent);
}
}
}
My App has a Fragment that's playing videos that are one second long. After a video is played the user has to press a button and then a new video is supposed to be played. Before playing the second video the my main activity loads creates a new fragment and creates new Exoplayer instance.
The first time I play the video everything works as expected. If I however want to play a second video, it shows a freezed image of the last frame of the first video and plays the sound of the second video.
This bug doesn't appear on Android 8 (API level 26) but on versions below Android 8 (I tested Android 6 and Android 7.1).
Given that I have both a new Surface with the new Fragment and a new Exoplayer it's a mystery to me why there's still data for the last frame of the previous video.
Is there some function I can call to delete that data and get the behavior I get in Android 8 where the second video plays without problems also in earlier versions of Android?
Before I used ExoPlayer the normal MediaPlayer had the same issue. I created a minimized example of the problem where I have:
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button mButton = findViewById(R.id.button1);
mButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
createNewFragment();
}
});
}
private void createNewFragment() {
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.add(R.id.contentFragment, new VideoFragment());
ft.commit();
}
}
public class VideoFragment extends Fragment implements SurfaceHolder.Callback{
private Context mContext;
private SimpleExoPlayer mPlayer;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.videolayout, container, false);
SurfaceView mSurfaceView = (SurfaceView) view.findViewById(R.id.surface_view1);
SurfaceHolder surfaceHolder = mSurfaceView.getHolder();
surfaceHolder.setSizeFromLayout();
surfaceHolder.addCallback(this);
mContext = inflater.getContext();
return view;
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
BandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
TrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory(bandwidthMeter);
TrackSelector trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory);
mPlayer = ExoPlayerFactory.newSimpleInstance(mContext, trackSelector);
mPlayer.setVideoSurfaceHolder(holder);
mPlayer.setPlayWhenReady(true);
playVideo(mContext);
}
private void playVideo(Context context){
final RawResourceDataSource rawResourceDataSource = new RawResourceDataSource(context);
int raw_res_id = context.getResources().getIdentifier(
"collect",
"raw",
context.getPackageName());
DataSpec dataSpec = new DataSpec(RawResourceDataSource.buildRawResourceUri(raw_res_id));
try {
rawResourceDataSource.open(dataSpec);
DataSource.Factory factory = new DataSource.Factory() {
#Override
public DataSource createDataSource() {
return rawResourceDataSource;
}
};
MediaSource media_source = new ExtractorMediaSource.Factory(factory).createMediaSource(rawResourceDataSource.getUri());
mPlayer.prepare(media_source);
} catch (RawResourceDataSource.RawResourceDataSourceException e) {
e.printStackTrace();
}
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
}
try to do the below code for fragmentchange
final FragmentTransaction transaction = fragmentManager.beginTransaction();
Fragment f = new VideoFragment();
transaction.replace(R.id.contentFragment, f).commit();
Just try this code and check if same occurs
The issue is that there is a delay when I try to change the orientation of the player. There is a lag of some 2 or 3 seconds before the video resumes. Every thing else works just fine except for the orientation change.
public class MainActivity extends AppCompatActivity {
private final String STATE_RESUME_WINDOW = "resumeWindow";
private final String STATE_RESUME_POSITION = "resumePosition";
private final String STATE_PLAYER_FULLSCREEN = "playerFullscreen";
private SimpleExoPlayerView mExoPlayerView;
private MediaSource mVideoSource;
private boolean mExoPlayerFullscreen = false;
private FrameLayout mFullScreenButton;
private ImageView mFullScreenIcon;
private Dialog mFullScreenDialog;
private int mResumeWindow;
private long mResumePosition;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState != null) {
mResumeWindow = savedInstanceState.getInt(STATE_RESUME_WINDOW);
mResumePosition = savedInstanceState.getLong(STATE_RESUME_POSITION);
mExoPlayerFullscreen = savedInstanceState.getBoolean(STATE_PLAYER_FULLSCREEN);
}
}
#Override
public void onSaveInstanceState(Bundle outState) {
outState.putInt(STATE_RESUME_WINDOW, mResumeWindow);
outState.putLong(STATE_RESUME_POSITION, mResumePosition);
outState.putBoolean(STATE_PLAYER_FULLSCREEN, mExoPlayerFullscreen);
super.onSaveInstanceState(outState);
}
private void initFullscreenDialog() {
mFullScreenDialog = new Dialog(this, android.R.style.Theme_Black_NoTitleBar_Fullscreen) {
public void onBackPressed() {
if (mExoPlayerFullscreen)
closeFullscreenDialog();
super.onBackPressed();
}
};
}
private void openFullscreenDialog() {
((ViewGroup) mExoPlayerView.getParent()).removeView(mExoPlayerView);
mFullScreenDialog.addContentView(mExoPlayerView, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT));
mFullScreenIcon.setImageDrawable(ContextCompat.getDrawable(MainActivity.this,R.drawable.ic_fullscreen_shrink));
mExoPlayerFullscreen = true;
mFullScreenDialog.show();
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
}
private void closeFullscreenDialog() {
((ViewGroup) mExoPlayerView.getParent()).removeView(mExoPlayerView);
((FrameLayout) findViewById(R.id.main_media_frame)).addView(mExoPlayerView);
mExoPlayerFullscreen = false;
mFullScreenDialog.dismiss();
mFullScreenIcon.setImageDrawable(ContextCompat.getDrawable(MainActivity.this,R.drawable.ic_fullscreen_expand));
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
private void initFullscreenButton() {
PlaybackControlView controlView = mExoPlayerView.findViewById(R.id.exo_controller);
mFullScreenIcon = controlView.findViewById(R.id.exo_fullscreen_icon);
mFullScreenButton = controlView.findViewById(R.id.exo_fullscreen_button);
mFullScreenButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (!mExoPlayerFullscreen)
openFullscreenDialog();
else
closeFullscreenDialog();
}
});
}
private void initExoPlayer() {
BandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
TrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory(bandwidthMeter);
TrackSelector trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory);
LoadControl loadControl = new DefaultLoadControl();
SimpleExoPlayer player = ExoPlayerFactory.newSimpleInstance(new DefaultRenderersFactory(this),trackSelector, loadControl);
mExoPlayerView.setPlayer(player);
boolean haveResumePosition = mResumeWindow != C.INDEX_UNSET;
if (haveResumePosition) {
mExoPlayerView.getPlayer().seekTo(mResumeWindow, mResumePosition);
}
mExoPlayerView.getPlayer().prepare(mVideoSource);
mExoPlayerView.getPlayer().setPlayWhenReady(true);
}
#Override
protected void onResume() {
super.onResume();
if (mExoPlayerView == null) {
mExoPlayerView = (SimpleExoPlayerView) findViewById(R.id.exoplayer);
initFullscreenDialog();
initFullscreenButton();
String streamUrl = "https://mnmedias.api.telequebec.tv/m3u8/29880.m3u8";
String userAgent = Util.getUserAgent(MainActivity.this, getApplicationContext().getApplicationInfo().packageName);
DefaultHttpDataSourceFactory httpDataSourceFactory = new DefaultHttpDataSourceFactory(userAgent, null,
DefaultHttpDataSource.DEFAULT_CONNECT_TIMEOUT_MILLIS,
DefaultHttpDataSource.DEFAULT_READ_TIMEOUT_MILLIS, true);
DefaultDataSourceFactory dataSourceFactory = new DefaultDataSourceFactory(MainActivity.this, null,httpDataSourceFactory);
Uri daUri = Uri.parse(streamUrl);
mVideoSource = new HlsMediaSource(daUri, dataSourceFactory, 1, null, null);
}
initExoPlayer();
if (mExoPlayerFullscreen) {
((ViewGroup) mExoPlayerView.getParent()).removeView(mExoPlayerView);
mFullScreenDialog.addContentView(mExoPlayerView, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
mFullScreenIcon.setImageDrawable(ContextCompat.getDrawable(MainActivity.this,
R.drawable.ic_fullscreen_shrink));
mFullScreenDialog.show();
}
}
#Override
protected void onPause() {
super.onPause();
if (mExoPlayerView != null && mExoPlayerView.getPlayer() != null) {
mResumeWindow = mExoPlayerView.getPlayer().getCurrentWindowIndex();
mResumePosition = Math.max(0, mExoPlayerView.getPlayer().getContentPosition());
mExoPlayerView.getPlayer().release();
}
if (mFullScreenDialog != null)
mFullScreenDialog.dismiss();
}
}
You should look into handling configuration changes yourself - as #marcbaechinger mentions above.
The necessary piece is here:
<activity android:name=".MyActivity"
android:configChanges="orientation|screenSize"
android:label="#string/app_name">
The most important part is:
Remember: When you declare your activity to handle a configuration change, you are responsible for resetting any elements for which you provide alternatives. If you declare your activity to handle the orientation change and have images that should change between landscape and portrait, you must re-assign each resource to each element during onConfigurationChanged().
But if you don't have any landscape specific layout files or images, then you'll likely be okay.
We had to write our own full screen logic for rotation, but that's simple enough considering Android gives you the configuration change event.
And to tack on a little extra validity to this approach, it's recommended by Google via the Youtube player documentation (specifically in regards to fullscreen):
To achieve this for an activity that supports portrait, you need to specify that your activity handles some configuration changes on its own in your application's manifest, including orientation, keyboardHidden and screenSize.
I'm using YouTubePlayerAPI and YouTubePlayerSupportFragment in my app and I'm getting the following error, but I couldn't find out what is causing it. I've been looking for information but I haven't found anything useful.
java.lang.IllegalStateException: YouTubeServiceEntity not initialized
at android.os.Parcel.readException(Parcel.java:1433)
at android.os.Parcel.readException(Parcel.java:1379)
at com.google.android.youtube.player.internal.l$a$a.a(Unknown Source)
at com.google.android.youtube.player.internal.o.a(Unknown Source)
at com.google.android.youtube.player.internal.ad.a(Unknown Source)
at com.google.android.youtube.player.YouTubePlayerView.a(Unknown Source)
at com.google.android.youtube.player.YouTubePlayerView$1.a(Unknown Source)
at com.google.android.youtube.player.internal.r.g(Unknown Source)
at com.google.android.youtube.player.internal.r$c.a(Unknown Source)
at com.google.android.youtube.player.internal.r$b.a(Unknown Source)
at com.google.android.youtube.player.internal.r$a.handleMessage(Unknown Source)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:5041)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:793)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:560)
at dalvik.system.NativeStart.main(Native Method)
In the stackstrace there isn't any line number pointing to any of my classes or activities.
Any idea of it?
Thanks!
EDIT
My custom YoutubePlayerFragment Class: YouTubeVideoPlayerFragment.java
public class YouTubeVideoPlayerFragment extends YouTubePlayerSupportFragment {
private static final String ARG_URL = "url";
// ===========================================================
// Constructors
// ===========================================================
/**
* Mandatory empty constructor for the fragment manager to instantiate the
* fragment (e.g. upon screen orientation changes).
*/
public YouTubeVideoPlayerFragment() {
}
/**
* Factory method to generate a new instance of the fragment given a video URL.
*
* #param url The video url this fragment represents
* #return A new instance of this fragment with itemId extras
*/
public static YouTubeVideoPlayerFragment newInstance(String url) {
final YouTubeVideoPlayerFragment mFragment = new YouTubeVideoPlayerFragment();
// Set up extras
final Bundle args = new Bundle();
args.putString(ARG_URL, url);
mFragment.setArguments(args);
// Initialize YouTubePlayer
mFragment.init();
return mFragment;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
private void init(){
initialize(Constants.API_KEY, new YouTubePlayer.OnInitializedListener() {
#Override
public void onInitializationSuccess(YouTubePlayer.Provider provider, YouTubePlayer youTubePlayer, boolean wasRestored) {
if (!wasRestored) {
youTubePlayer.cueVideo(getArguments().getString(ARG_URL));
youTubePlayer.setShowFullscreenButton(false);
}
}
}
fragment.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
android:layout_height="match_parent"
android:layout_width="match_parent"
android:background="#color/black" >
<!-- For YoutubeFragment -->
<FrameLayout
android:id="#+id/youtube_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
calling method:
// Create a new instance of YouTubeVideoPlayerFragment providing video id
// and place it in the corresponding FrameLayout
final YouTubeVideoPlayerFragment youTubeVideoPlayerFragment = YouTubeVideoPlayerFragment.newInstance(VIDEO_ID);
final FragmentTransaction ft = getChildFragmentManager().beginTransaction();
ft.replace(R.id.youtube_fragment, youTubeVideoPlayerFragment);
ft.commit();
EDIT
I've found out the origin of that error. This is the scenario:
The activity starts. In onCreate() it instantiates a new YouTubeVideoPlayerFragment and initializes YouTube object (which starts the YouTubeServiceEntity internally) in its newInstance() method. Then the YouTube fragment that was instantiated before, is attached with FragmentManager to the corresponding FrameLayout while video is loading.
Here is the issue: If user exits the activity before video had been loaded, the exception is thrown.
So if user want to exit from the activity in that case, what should I do and how? I don't really know what to do!
Once again, do NOT use fragment constructors or factory methods to work with lifecycle or context bound entities. Simply put, such entities can only be used after super.onCreate(...) has been called.
The question now is, when to call the init method?
Here's what YouTubePlayerFragment documentation says:
The YouTubePlayer associated with this fragment will be released whenever its onDestroyView() method is called. You will therefore have to re-call initialize(String, YouTubePlayer.OnInitializedListener) whenever the activity associated with this fragment is recreated, even if the fragment instance is retained across activity re-creation by setting setRetainInstance(boolean).
You may be tempted to put init() in onActivityCreated but that's too late, since onStart was already called and layout already performed.
Counterpart to onDestroyView is onViewCreated and that's the perfect candidate.
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
init();
}
As suggested call setRetainInstance(true) in the fragment's constructor. When the activity is recreated the fragment will not be recreated, only its UI will go through lifecycle events.
I see the same error reported from a Huawei cellphone.
I see an explanation here:
https://github.com/youtube/yt-android-player/issues/23
Not sure if there is a way to catch the exception in our code.
The problem is the initialization of the Youtube fragment. YouTubePlayerSupportFragment has to be extended in a class of yours and overrides some methods. You have to control the screen orientation and the onSaveInstanceState.
public class YouTubePlayerFragment extends YouTubePlayerSupportFragment {
private YouTubePlayer mPlayer;
public static YouTubePlayerFragment newInstance() {
return new YouTubePlayerFragment();
}
#Override public void onCreate(Bundle bundle) {
super.onCreate(bundle);
setRetainInstance(true);
}
#Override
public void initialize(String s, YouTubePlayer.OnInitializedListener onInitializedListener) {
super.initialize(s, new YouTubePlayer.OnInitializedListener() {
#Override public void onInitializationSuccess(YouTubePlayer.Provider provider,
YouTubePlayer youTubePlayer, boolean b) {
mPlayer = youTubePlayer;
onInitializedListener.onInitializationSuccess(provider, youTubePlayer, b);
}
#Override public void onInitializationFailure(YouTubePlayer.Provider provider,
YouTubeInitializationResult youTubeInitializationResult) {
onInitializedListener.onInitializationFailure(provider, youTubeInitializationResult);
}
});
}
#Override public void onDestroyView() {
if (mPlayer != null) {
mPlayer.release();
}
super.onDestroyView();
}
public YouTubePlayer getPlayer() {
return mPlayer;
}
}
YoutubeFragment.class
public class YoutubeFragment extends Fragment {
private static final String EXTRA_PLAYED_VIDEO = "EXTRA_PLAYED_VIDEO";
private static final String EXTRA_IS_PLAYING = "EXTRA_IS_PLAYING";
private static final String YOUTUBE_FRAGMENT = "YOUTUBE_FRAGMENT";
private static final String EXTRA_YOUTUBE_ID = "EXTRA_YOUTUBE_ID";
private RelativeLayout youtubeLayoutContainer;
private String youtubeId;
private int playedVideo;
private boolean isPlaying;
YouTubePlayer.OnInitializedListener onInitializedListener =
new YouTubePlayer.OnInitializedListener() {
#Override
public void onInitializationSuccess(YouTubePlayer.Provider provider, YouTubePlayer player,
boolean wasRestored) {
if (!wasRestored) {
setYouTubePlayer(player);
}
}
#Override public void onInitializationFailure(YouTubePlayer.Provider provider,
YouTubeInitializationResult error) {
}
};
public static YoutubeFragment newInstance(String youtubeId) {
YoutubeFragment youtubeElements = new YoutubeFragment();
Bundle bundle = new Bundle();
bundle.putString(EXTRA_YOUTUBE_ID, youtubeId);
youtubeElements.setArguments(bundle);
return youtubeElements;
}
#Override public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
#Nullable #Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container,
#Nullable Bundle savedInstanceState) {
View mView = inflater.inflate(R.layout.view_youtube_elements_item, container, false);
initViews(mView);
initYoutubeFragment();
return mView;
}
private void initViews(View view) {
youtubeLayoutContainer = (RelativeLayout) view.findViewById(R.id.youtubeLayoutContainer);
youtubeLayoutContainer.getViewTreeObserver()
.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#TargetApi(Build.VERSION_CODES.JELLY_BEAN) #Override public void onGlobalLayout() {
FrameLayout.LayoutParams lp =
new FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT,
FrameLayout.LayoutParams.MATCH_PARENT);
youtubeLayoutContainer.setLayoutParams(lp);
if (AndroidSdkVersion.hasJellyBean16()) {
youtubeLayoutContainer.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
}
});
}
private void initYoutubeFragment() {
try {
YouTubePlayerFragment youTubePlayerFragment2 = YouTubePlayerFragment.newInstance();
youTubePlayerFragment2.initialize(BuildConfig.YOUTUBE_DEVELOPER_KEY, onInitializedListener);
if (this.getActivity() != null && !this.getActivity().isFinishing()) {
getChildFragmentManager().beginTransaction()
.replace(R.id.youtubePlayerFragmentContent, youTubePlayerFragment2, YOUTUBE_FRAGMENT)
.commitAllowingStateLoss();
}
} catch (Exception ignored) {
}
}
public void setYouTubePlayer(final YouTubePlayer player) {
try {
if (player == null) {
return;
}
player.setShowFullscreenButton(true);
player.setPlayerStyle(YouTubePlayer.PlayerStyle.DEFAULT);
if (playedVideo >= 0) {
if (playedVideo == 0 || isPlaying) {
player.loadVideo(youtubeId, playedVideo);
} else {
player.cueVideo(youtubeId, playedVideo);
}
}
} catch (Exception ignored) {
}
}
#Override public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
if (savedInstanceState != null) {
playedVideo = savedInstanceState.getInt(EXTRA_PLAYED_VIDEO);
isPlaying = savedInstanceState.getBoolean(EXTRA_IS_PLAYING);
}
}
#Override public void onSaveInstanceState(Bundle outState) {
try {
YouTubePlayerFragment youTubePlayerSupportFragment =
(YouTubePlayerFragment) getChildFragmentManager().findFragmentByTag(YOUTUBE_FRAGMENT);
YouTubePlayer mPlayer = youTubePlayerSupportFragment.getPlayer();
if (mPlayer != null) {
outState.putInt(EXTRA_PLAYED_VIDEO, mPlayer.getCurrentTimeMillis());
outState.putBoolean(EXTRA_IS_PLAYING, mPlayer.isPlaying());
}
} catch (Exception ignored) {
}
super.onSaveInstanceState(outState);
}
}
Activity containing Youtube Fragment
public class YoutubeContentDataActivity extends BaseActivity {
private static final String EXTRA_YOUTUBE_VIDEO_ID = "EXTRA_YOUTUBE_VIDEO_ID";
private static final String TAG_RETAINED_FRAGMENT = "TAG_RETAINED_FRAGMENT";
public static void open(Context context, String videoId) {
Intent intent = new Intent(context, YoutubeContentDataActivity.class);
intent.putExtra(EXTRA_YOUTUBE_VIDEO_ID, videoId);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
#Override public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_youtube_main_container_layout);
FragmentManager fm = getSupportFragmentManager();
YoutubeFragment youtubeElementsFragment =
(YoutubeFragment) fm.findFragmentByTag(TAG_RETAINED_FRAGMENT);
// create the fragment and data the first time
if (youtubeElementsFragment == null) {
String videoId = getIntent().getStringExtra(EXTRA_YOUTUBE_VIDEO_ID);
// videoId = "17uHCHfgs60";//"ikO91fQBsTQ";
youtubeElementsFragment = YoutubeFragment.newInstance(videoId);
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
.replace(R.id.youtube_main_container, youtubeElementsFragment, TAG_RETAINED_FRAGMENT)
.commit();
}
}
#Override public void onPause() {
super.onPause();
if (isFinishing()) {
FragmentManager fm = getSupportFragmentManager();
YoutubeFragment youtubeElementsFragment =
(YoutubeFragment) fm.findFragmentByTag(TAG_RETAINED_FRAGMENT);
fm.beginTransaction().remove(youtubeElementsFragment).commit();
}
}
}
I could fix this error by using a ProgressDialog to wait until the player is loaded.