I am trying to restore audio to the position it was and the file it was when the user left the fragment. To do this I save the location of the audio file, and the seek position using personal prefs, along with a boolean for whether or not the audio was playing when the user left. I save this info first thing in onPause().
When I resume, I initialize the views etc. and the very last thing I do in onResume is read from personal prefs and play the audio stored there is appropriate.
However when I try to play from onResume, the media completion listener gets called immediately and the file gets skipped.
I have been running tests and I know that the media player is handed the right data, is prepared correctly and set to play.
The way I am trying to play the audio is the same way I do it if a user clicks manually to play audio, and that works flawlessly.
Only when trying to 'restore' the audio to where it was when a user left does the completion listener get called immediately.
Has anyone seen this before?
public void setAudioURLAndPLay(Context context, String url)
{
Log.d(TAG, "setAudioURLAndPLay");
CacheQueue.getInstance().addImmediateTaskToQueue(CacheQueue.AUDIO_TASK, context, url, 0, handler);
}
private void playCahcedFile(String location)
{
Log.d(TAG, "playCahcedFile");
try
{
this.reset();
this.setAudioStreamType(AudioManager.STREAM_MUSIC);
this.setDataSource(location);
this.setOnPreparedListener(new OnPreparedListener()
{
#Override
public void onPrepared(MediaPlayer mp)
{
setPlay();
}
});
this.prepareAsync();
}
catch (Exception e)
{
Log.d(TAG, "Exception", e);
}
}
public void setPlay()
{
Log.d(TAG, "setPlay");
this.start();
this.setProgressHandler(this.listener);
}
and where the calls are being made
public void initializeFromResume()
{
PersonalPrefs prefs = new PersonalPrefs(getActivity());
if (!prefs.isPLaying())
{
return;
}
else
{
playNewAudio(prefs.getURL());
// ((ActivityMain) getActivity()).getMediaManager().setSeek(prefs.getSeek());
}
}
private void playNewAudio(String url)
{
getMediaManager().setAudioURLAndPLay(getActivity(), url)
mediaState = MediaState.playing;
initializeSeekBar();
getMediaManager().setOnCompletionListener(this);
mediaController.togglePlayButton(mediaState);
}
I figured it out and will post the answer to anyone who has similar troubles in the future.
Just need to run a post delayed. Not exactly amazing, but it works.
h.postDelayed(new Runnable()
{
#Override
public void run()
{
PersonalPrefs prefs = new PersonalPrefs(getActivity());
playNewAudio(prefs.getURL());
}
}, 1000);
Related
I am making a chat application and I have implemented the feature for sending audio messages.But here I find one thing which I don't want it to happen.It is that whenever my adapter gets updated,The media player starts loading again. In this way there will be an issue for if someone is listening to an audio and the user at other end sends a message ,the media player stops and it loads again.Here is the code of my adapter.
final MediaPlayer mediaPlayer;
mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
handler = new Handler();
try {
mediaPlayer.setOnCompletionListener(mediaPlayer1 -> {
mediaPlayer1.stop();
binding.audioSeekbar.setProgress(0);
});
if (mediaPlayer.isPlaying()){
mediaPlayer.stop();
mediaPlayer.release();
}
mediaPlayer.setDataSource(finalUrlToLoad[1]);
mediaPlayer.setVolume(1f, 1f);
mediaPlayer.prepareAsync();
mediaPlayer.setOnPreparedListener(mediaPlayer1 -> {
int totalDuration = mediaPlayer1.getDuration();
binding.totalDurationAudio.setText(createTimeLabel(totalDuration));
binding.loadingAudio.setVisibility(GONE);
binding.playPauseAudio.setVisibility(VISIBLE);
});
} catch (IOException e) {e.printStackTrace();}
binding.playPauseAudio.setOnClickListener(view -> {
if (mediaPlayer.isPlaying()){
handler.removeCallbacks(runnable);
mediaPlayer.pause();
binding.playPauseAudio.setImageResource(R.drawable.pause_to_play);
Drawable drawable = binding.playPauseAudio.getDrawable();
if( drawable instanceof AnimatedVectorDrawable) {
AnimatedVectorDrawable animation = (AnimatedVectorDrawable) drawable;
animation.start();
}
}else {
mediaPlayer.seekTo(binding.audioSeekbar.getProgress());
mediaPlayer.start();
handler.post(runnable);
binding.playPauseAudio.setImageResource(R.drawable.play_to_pause);
Drawable drawable = binding.playPauseAudio.getDrawable();
if( drawable instanceof AnimatedVectorDrawable) {
AnimatedVectorDrawable animation = (AnimatedVectorDrawable) drawable;
animation.start();
}
}
});
runnable = () -> {
int totalTime = mediaPlayer.getDuration();
binding.audioSeekbar.setMax(totalTime);
int currentPosition = mediaPlayer.getCurrentPosition();
binding.audioSeekbar.setProgress(currentPosition);
binding.totalDurationAudio.setText(createTimeLabel(totalTime));
Log.d("time", String.valueOf(currentPosition));
handler.postDelayed(runnable,1000);
};
binding.audioSeekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
#Override
public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
if (b){
mediaPlayer.seekTo(i);
seekBar.setProgress(i);
}
}
#Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
#Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
mediaPlayer.setOnBufferingUpdateListener((mediaPlayer1, i) -> binding.audioSeekbar.setSecondaryProgress(i));
Here finalurltoload[1] is the url for the audio.
Now what do I need to do in order to prevent loading it again and again.
I will be really grateful to who answer this question.
Thanks😊.
It's hard to tell from this code but I assume this is all set in your onBind event? If so, then this means every time RecyclerView creates a new holder and binds it, the associated media will be prepped and loaded, and whichever is the 'last holder to have been called with onBind, "wins" (and is what MediaPlayer will be loaded with). Since by default RecyclerView typically creates multiple holders up front, you are seeing your MediaPlayer being "loaded" multiple times.
You probably just don't want to do the initialization of each audio message in the onBind. Instead, just use the onBind event to initialize state variables (duration, progress, etc.) to some default value, hide them and bind the specific audio Uri. Then when the user takes some action like tapping on the holder, you unhide an indeterminate progress bar while the initialization takes place, and in the onPrepared() event unhide the state information (duration, progress, seekbar, etc.), and finally hide the indeterminate progress bar and start the audio.
I assume you are also sending over the sound file as part of your messaging app (i.e. not storing it on the web somewhere in a central location?), and this file gets stored in an app-specific storage location? If so, you don't need to worry about persisting the permission to that URI, but if that isn't the case you will.
First extract the media player code into singleton class like AudioManager.
Add few method like setMediaUpdateListener that set a callback for seek duration. and togglePlayPause to play or pause the audio.
Passed the message id or any unique identifier to the audio manager while playing the video.
In Adapter class onBind Method.
First Compare the id and playing Id is same like AudioManager.getInstance().isPlaying(messageId);
If yes then set the seekUpdatelistner to the audio manager class.
also update the play/pause icon based on AudioManager.isPlaying() method.
3.if user play other message by clicking play button. call AudioManager.play(message) method.In which we release the previous message and play the new one.
If current message is not playing then reset the view on non-playing state.
If Auto play is enabled then you need to check if audioManager is free then only you can play the last message otherwise ignored.
Like a class who are managing the audio for you and store all the state.
class AudioManager {
public static AudioManager instance;
final MediaPlayer mediaPlayer;
private AudioListener audioListener;
private Uri currentPlaying;
public AudioManager getInstance() {
if (instance == null) {
instance = new AudioManager();
}
}
public void play(Uri dataUri) {
if (mediaPlayer != null && currentPlaying == null || currentPlaying.equals(dataUri)) {
if (!mediaPlayer.isPlaying) {
mediaPlayer.play();
}
return;
} else if (mediaPlayer != null) {
mediaPlayer.stop();
mediaPlayer.release();
}
mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
handler = new Handler();
try {
mediaPlayer.setOnCompletionListener(mediaPlayer1 -> {
mediaPlayer1.stop();
sendProgress(0);
});
if (mediaPlayer.isPlaying()) {
mediaPlayer.stop();
mediaPlayer.release();
}
mediaPlayer.setDataSource(dataUri;
mediaPlayer.setVolume(1f, 1f);
mediaPlayer.prepareAsync();
mediaPlayer.setOnPreparedListener(mediaPlayer1 -> {
int totalDuration = mediaPlayer1.getDuration();
sendTotalDuration(totalDuration);
});
} catch (IOException e) {
e.printStackTrace();
}
}
public void pause() {
// update the pause code.
}
public void sendProgress(int progress) {
if (audioListener != null) {
audioListener.onProgress(progress);
}
}
public void sendTotalDuration(int duration) {
if (audioListener != null) {
audioListener.onTotalDuraration(duration);
}
}
public void AudioListener(AudioListener audioListener) {
this.audioListener = audioListener;
}
public interface AudioListener {
void onProgress(int progress);
void onTotalDuraration(int duration);
void onAudioPlayed();
void onAudioPaused():
}
}
I want to run AnimatedVectorDrawable animation on infinite loop.
final AnimatedVectorDrawableCompat avd = AnimatedVectorDrawableCompat.create(this, R.drawable.avd_anim_happy);
mImageView.setImageDrawable(avd);
// animation on infinite loop
avd.registerAnimationCallback(new Animatable2Compat.AnimationCallback() {
#Override
public void onAnimationStart(Drawable drawable) {
super.onAnimationStart(drawable);
Log.e(TAG, "onAnimationStart() called");
}
#Override
public void onAnimationEnd(Drawable drawable) {
Log.e(TAG, "onAnimationEnd() called");
avd.start();
}
});
avd.start();
}
This works fine when I tested with Nougat device. But when I try to run this on KitKat device, callback is called but animation is not started again. There is no
"onAnimationStart() called" log message. But upon doing the following.
#Override
public void onAnimationEnd(Drawable drawable) {
Log.e(TAG, "onAnimationEnd() called");
if (avd.isRunning()) {
Log.e(TAG, "avd running!!");
avd.stop();
}
avd.start();
}
onAnimationEnd() and onAnimationStart() are getting called again and again. But for some reason animation just stopped after playing for one time.
I was having the same issue with old platforms, turns out i work around it by posting the start after the end event.
imageView.post(new Runnable() {
#Override
public void run() {
avd.start();
}
});
It has been a long time since I stopped by this problem: my FileObserver's onEvent method is not triggered, tested, and not even the "method entered" toast is being displayed.
FileObserver fileObserver = new FileObserver(android.os.Environment.getExternalStorageDirectory().toString() + "/Pictures/Screenshots") {
#Override
public void onEvent(int event, String path) {
Toast.makeText(getApplicationContext(), "method entered", Toast.LENGTH_SHORT).show();
if (event == FileObserver.CREATE) {
handler.post(new Runnable() {
#Override
public void run() {
Toast.makeText(getApplicationContext(), "File created", Toast.LENGTH_SHORT).show();
}
});
}
}
};
fileObserver.startWatching();
Help me please! Thanks in advance.
Check existence of file prev, it should cause issue.
public void startWatching ()
Added in API level 1 Start watching for events. The monitored file or
directory must exist at this time, or else no events will be reported
(even if it appears later). If monitoring is already started, this
call has no effect.
I have an app that uses the CastCompanionLibrary and I'm having a weird issue. On a small network with one chromecast device my application is able to detect it and show the MediaRouterItem.
Although, I had a beta tester say that they were not able to get any of their devices detected, so the icon never shows up for them. Come to find out they are connected to a larger, shared network with multiple chromecast devices connected to it. They said that they are able to detect all of the chromecast devices with other apps such as YouTube and Localcast though. Which is weird because that leads me to believe that maybe I'm not doing something right with the discovery process.
Unfortunately, I am not in a position where I can have a network with multiple chromecast devices to debug this issue, so I was just wondering if anyone else had a similar issue? Or is there a certain method that you have to call with the CastCompanionLibrary that I'm missing?
EDIT
I am using the APP_ID that has been published, so I know that it isn't a whitelisting issue.
The code I use for discovery is completed by the CastCompanionLibrary. This is what I use once the onCastDeviceDetected() callback is called:
mCastConsumer = new VideoCastConsumerImpl() {
#Override
public void onFailed(int resourceId, int statusCode) {
}
#Override
public void onConnectionSuspended(int cause) {
Log.d(TAG, "onConnectionSuspended() was called with cause: " + cause);
}
#Override
public void onConnectivityRecovered() {
}
#Override
public void onCastDeviceDetected(final MediaRouter.RouteInfo info) {
if (!MyApplication.isFtuShown(Home.this)) {
MyApplication.setFtuShown(Home.this);
Log.d(TAG, "Route is visible: " + info);
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
if (mediaRouteMenuItem.isVisible()) {
Log.d(TAG, "Cast Icon is visible: " + info.getName());
//showFtu();
}
}
}, 1000);
}
}
};
MyApplication.class:
public static boolean isFtuShown(Context ctx) {
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(ctx);
return sharedPref.getBoolean(FTU_SHOWN_KEY, false);
}
public static void setFtuShown(Context ctx) {
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(ctx);
sharedPref.edit().putBoolean(FTU_SHOWN_KEY, true).commit();
}
I have placed the parse method inside onCreate method. But my problem is how to show the Android Loading... Dialog??
Parse.initialize(this, "a", "b");
ParseQuery query = new ParseQuery("Category");
query.findInBackground(new FindCallback() {
#Override
public void done(List<ParseObject> catObjects, ParseException arg1) {
Log.d("Catlength", String.valueOf(catObjects.size()));
for(int i =0; i<catObjects.size(); i++){
Log.d("lengthName"+String.valueOf(i), String.valueOf(catObjects.get(i).getInt("Id")));
Category category = new Category();
category.Name= catObjects.get(i).getString("CatName");
category.id= catObjects.get(i).getInt("Id");
categories.add(category);
}
if(categories.size()>0){
setListAdapter(new CategoryArrayAdapter(CategoryListActivity.this, R.layout.row_category, categories));
}
else{
Toast.makeText(CategoryListActivity.this, "Our servers are busy. Hit refresh..", 3000).show();
}
}
});
Everything works fine in the above code but I couldn't figure out how to show the Dialog.
I'm unable to use AsycTask also as parse sdk invokes its own thread in the background and before the findInBackground execution finishes, the doInBackground completes the Asyc thread. That's why I invoked it in the main thread.
As the result I always get no results in my ArrayList.
Can someone please enlighten me.
I was in the same situation regarding the progress dialog, tried a few tricks and finally just declared a ProgressDialog class member:
protected ProgressDialog proDialog;
then created two methods:
protected void startLoading() {
proDialog = new ProgressDialog(this);
proDialog.setMessage("loading...");
proDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
proDialog.setCancelable(false);
proDialog.show();
}
protected void stopLoading() {
proDialog.dismiss();
proDialog = null;
}
and called startLoading() before the background operation and stopLoading()
inside the background operation after I got the the results.
startLoading();
ParseUser.logInInBackground(userName.getText().toString(), hashedPass, new LogInCallback() {
public void done(ParseUser user, ParseException e) {
if (user != null) {
Log.d(Constants.TAG, "User Loged in.");
ParseManager.sCurrentUser = user;
stopLoading();
finish();
} else {
stopLoading();
invalidCreds();
}
}
});
if you want to use AsyncTask don't call findInBackground() you can use find().
you can check it out in the api https://parse.com/docs/android/api/com/parse/ParseQuery.html#find()
hope this helps.
It's easy to get the progress of both uploads and downloads using ParseFile by passing a ProgressCallback to saveInBackground and getDataInBackground. For example:
byte[] data = "Working at Parse is great!".getBytes();
ParseFile file = new ParseFile("resume.txt", data);
file.saveInBackground(new SaveCallback() {
public void done(ParseException e) {
// Handle success or failure here ...
}
}, new ProgressCallback() {
public void done(Integer percentDone) {
// Update your progress spinner here. percentDone will be between 0 and 100.
}
});