In the main activity of my app, I show a number of cards to user. when he clicks on any of them, a fragment is opened which contains a ViewPager2. The fragments inside the viewPager2 each contains an ExoPlayer and plays a related video when resumed.
When the user clicks on back button, the fragment must be closed and release all its children (including viewPager2 and its fragments, and exoPlayers). In order to release exoPlayers, I use the following code in OnDestroyView() of the viewPager2 fragments:
#Override
public void onDestroyView() {
super.onDestroyView();
try {
if (player != null && listener != null) {
player.removeListener(listener);
player.release();
player = null;
}
if (playerView != null)
playerView.setPlayer(null);
dashMediaSource = null;
progressiveMediaSource = null;
playerView = null;
listener = null;
} catch (Exception ignored) {
}
}
after that, I ran Android Studio profiler to check app heap and I noticed some objects related to exoPlayer are still in memory:
Now the question is are I making a mistake in releasing the memory and specifically exoPlayers? If not why these objects are still in RAM and how to get rid of them?
Related
I am learning how to play audio using MediaPlayer from this tutorial, which suggests using release() instead of stop() to STOP the audio. His explanation makes sense to me (free up the system resource as soon as you don't need it) and from a user perspective it works as expected, but I still feel like a bit weird that what's the point of using stop()? (https://stackoverflow.com/a/20580149/3466808)
fun stopPlayer1() = mediaPlayer?.stop()
fun stopPlayer2() {
mediaPlayer?.release()
mediaPlayer = null
}
So, which approach is better? Release as soon as user stops the audio? Or release only when the screen is no longer visible (onStop() called)?
take a look at the diagram in DOCS
MediaPlayer after release() is not "usable" anymore, you can nullify it safely. after onStop you still can call e.g. prepareAsync() and start playing again using single instance
edit: to comment
if (mMediaPlayer != null) {
try {
mMediaPlayer.stop();
} catch (Exception ignored) {
}
try {
mMediaPlayer.reset();
} catch (Exception ignored) {
}
try {
mMediaPlayer.release();
} catch (Exception ignored) {
}
mMediaPlayer = null;
}
I currently have an app (Java) that uses the Navigation Component with Bottom Navigation. I have a fragment for each tab.
One of the fragments navigates to a secondary fragment that features an audio player (using Media Player). This audi fragment has the following code that stops the audio when I go back to the parent fragment:
#Override
public void onDestroy() {
super.onDestroy();
if (mp != null) {
mp.pause();
mp = null;
}
}
The problem is that when the audio is playing and I tap on a different tab from the bottom navigation, the audio continues to play. I thought of using the following:
#Override
public void onStop() {
super.onStop();
if (mp != null) {
mp.pause();
mp = null;
}
}
And this solves the problem but it also prevents the audio from playing when the screen is turned off. (This doesn't happen when the onStop is not there).
The idea is to have the pause() function execute when the user taps on any of the bottom navigation tabs.
With my current structure, how can I get the sound to stop when the user taps on a bottom tab?
You can use "setOnItemSelectedListener()" to listen for clicks on Bottom Navigation.
If the user selects an item other than the one inside the audio player, stopAudioPlayer function is called
Example :-
navBottom.setOnNavigationItemSelectedListener(item -> {
if (item.getItemId() != R.id.nav_bottom_audio_player)
stopAudioPlayer();
return true;
});
I have a RecyclerView with some video elements and they get restarted every time they get off-screen. I tried:
recyclerView.getRecycledViewPool().setMaxRecycledViews(RecyclerViewAdapter.TYPE_VIDEO, 0);
but no success. I also tried to do:
holder.setIsRecyclable(false)
inside my adapter, but videos still restart every time.
Is there any way to stop restarting videos, and somehow pause them and resume them once they are on screen again?
The videos are remote, not local. And I am using a class extending TextureView, not the Android's VideoView
Edit: I missunderstood the question. See original answer below.
I guess you have two possibilities:
Ugly, only possible if the list is not going to be too long™ and undermining the purpose of the recyclerview: use setItemViewCacheSize(int) to increase the number of viewholders that are cached to the number of videos you have and just pause and play during attach / detach.
much better: Store and restore the current playback timestamp of each video when the viewholder gets attached/dettached.
You can use an RecyclerView.OnChildAttachStateChangeListener that will be called whenever a view is attached / removed.
Do it like this:
mRecyclerView.addOnChildAttachStateChangeListener(new RecyclerView.OnChildAttachStateChangeListener() {
#Override
public void onChildViewAttachedToWindow(View view) {
YourTextureView yourTextureView = (YourTextureView) view.findViewById(R.id.yourvideoviewid);
if (yourTextureView != null) {
// start your video
}
}
#Override
public void onChildViewDetachedFromWindow(View view) {
YourTextureView yourTextureView = (YourTextureView) view.findViewById(R.id.yourvideoviewid);
if (yourTextureView != null) {
// stop your video
}
}
});
I want to have two different background music loops playing depending on the state of the app. To do so I tried that code:
private void backgroundMusicPlayer() {
if (gameMode == 0) {
if (backgroundloop2 != null) {
backgroundloop2.pause();
backgroundloop2.stop();
backgroundloop2.release();
backgroundloop2 = null;
}
backgroundloop1 = MediaPlayer.create(getContext(), R.raw.gameloop1);
backgroundloop1.setLooping(true);
backgroundloop1.start();
}
else {
if (backgroundloop1 != null) {
backgroundloop1.pause();
backgroundloop1.stop();
backgroundloop1.release();
backgroundloop1 = null;
}
backgroundloop2 = MediaPlayer.create(getContext(), R.raw.gameloop2);
backgroundloop2.setLooping(true);
backgroundloop2.start();
}
}
But I just get errors:
"MediaPlayer: start called in state 64" "MediaPlayer: pause called in
state 8" "Failed to open libwvm.so: dlopen failed: library "libwvm.so"
not found" "Media Player called in state 0, error (-38,0)"
How can I do it properly?
Why IllegalStateException in onPause()?
In Android Documentation for onPause:
IllegalStateException
If the internal player engine has not been initialized.
In the document, you can see a maximum of media player methods would throw you IllegalStateException for some reason.
So use try catch for all of your media player operations.
Android recommends looking for Exceptions while using media player object.
It is good programming practice to always look out for
IllegalArgumentException and IOException that may be thrown from the
overloaded setDataSource methods.
I have two fragments that has a TextureView to show camera preview or to play video.
after using the app for a while, playing with the screens,
i get this error in the logcat
OpenGLRenderer﹕ GL_INVALID_OPERATION
i release everything from my fragments,
all members are set to null.
#Override
public void onDestroyView() {
Logg.DEBUG(TAG, "onDestroyView");
super.onDestroyView();
if (mMediaPlayer != null) {
mMediaPlayer.stop();
mMediaPlayer.release();
mMediaPlayer = null;
}
nextButton = null;
pauseButton = null;
backButton = null;
playButton = null;
frontTextView = null;
backTextView = null;
surface = null;
videoView = null;
}
and i see the whole view become weird...
what am i missing?
Your screenshot shows situation when system OpenGL context is corrupted / broken. Please check on what thread you release your resouces. GLContext should be destroyed from exactly same thread where it was allocated. In your case it could be setSurface/setDisplay calls made from wrong thread.
If you have stable and easy steps to reproduce you can try to capture GL log using Tracer for OpenGL ES, but its slows your application a lot during capturing