1

This is my Firebase Database structure I'm very new to Android development and Firebase. I have some experience in JavaFX. I'm trying to build a small app that users can listen to music together synchronously. I use MediaPlayer library to stream music from Firebase storage. But in order to play the music (MediaPlayer object), MediaPlayer object has to been initialized. Whenever I try to play the music, I get a runtime error that MediaPlayer object has not been initialized due to Firebase delay. This is because Firebase methods are asynchronous. I want a method that actually starts the music to wait until MediaPlayer object has been initialized. I've seen some inner interface callback mechanism. But I couldn't make it.

This is my code:

DatabaseReference myRef;
String myUri="https://firebasestorage.googleapis.com/v0/b/listentest-1297.appspot.com/o/Musics%2Fsong.mp3?alt=media&token=23626e51-e062-469e-9204-8405a14051fd";
MediaPlayer mediaPlayer;
boolean isFirstTimePlayClick = true;
int isPlayingFirebase = 0;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_third);
    myRef = FirebaseDatabase.getInstance().getReference("MusicRooms");

   /*
    FirebaseStorage storage = FirebaseStorage.getInstance();
    StorageReference storageRef = storage.getReferenceFromUrl("gs://listentest-1297.appspot.com/Musics/song.mp3");
    storageRef.getDownloadUrl().addOnSuccessListener(new OnSuccessListener<Uri>() {
        @Override
        public void onSuccess(Uri uri) {
            myUri = uri.toString();
        }
    });*/
    myRef.child(secondActivity.musicRoomID).addValueEventListener(new ValueEventListener() {
        @Override
        public void onDataChange(@NonNull DataSnapshot dataSnapshot) {
            isPlayingFirebase = dataSnapshot.getValue(MusicRoom.class).getIsPlaying();

            playResume();
        }

        @Override
        public void onCancelled(@NonNull DatabaseError databaseError) {
        }
    });

}

public void playPauseBtn(View view) {
    if (isPlayingFirebase == 1) {
        myRef.child(secondActivity.musicRoomID).child("isPlaying").setValue(0);

    } else {
        myRef.child(secondActivity.musicRoomID).child("isPlaying").setValue(1);
    }
}

public void playResume() {

    if (isFirstTimePlayClick) {
        try {
            mediaPlayer = new MediaPlayer();
            mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
            mediaPlayer.setDataSource(myUri);
            mediaPlayer.prepare();
            mediaPlayer.start();
            isFirstTimePlayClick = false;
        } catch (Exception e) {
            Toast.makeText(getApplicationContext(), e.getMessage(), Toast.LENGTH_SHORT).show();
        }

    } else{
        if(mediaPlayer.isPlaying()){
            mediaPlayer.pause();
        }
        else{
            mediaPlayer.start();
        }
    }
}
Teyyihan Aksu
  • 156
  • 2
  • 10

2 Answers2

0

U can do it with livedata: create status class

enum class Status {
    RUNNING,
    SUCCESS,
    FAILED
}

then when u want to load music from firebase:

fun loadMusic(musicId: String) {
        livedata.value = Status.RUNNING
        musicReference.document(musicId).get()
            .addOnSuccessListener {documentSnapshot ->
                mediaPlayerObject = documentSnapshot.toObject(MediaPlayerObject::class.java)
                livedata.value = Status.SUCCESS
            }
            .addOnFailureListener {
                livedata.value = Status.FAILED
            }
}

and set observer on liveData

        livedata.observe(this, Observer {
            if(it == Status.SUCCESS){
                mediaPlayerObject.play()
            }
        })

EDIT

or here is a version in Java with the interface

    interface StatusListener {
        void onRunning()
        void onSuccess()
        void onFailed()
    }
        statusListener = new StatusListener () {
            @Override
            public void onRunning() {}

            @Override
            public void onSuccess() {
                mediaPlayerObject.play();
            }

            @Override
            public void onFailed() {}
        };
    void loadMusic(String musicId) {
        statusListener.onRunning();
        musicReference.document(musicId).get()
                .addOnSuccessListener(documentSnapshot -> {
                    mediaPlayerObject = documentSnapshot.toObject(MediaPlayerObject.class);
                    statusListener.onSuccess();
                })
                .addOnFailureListener(e -> {
                    statusListener.onFailed();
                });
    }
Artur Gniewowski
  • 470
  • 7
  • 17
  • I will try that, thanks. But i have no expriences in enum's. – Teyyihan Aksu Aug 02 '19 at 11:29
  • if u don't want to use enums and livedata u can create callback with method success and failed. – Artur Gniewowski Aug 02 '19 at 11:35
  • Like this? https://stackoverflow.com/questions/47847694/how-to-return-datasnapshot-value-as-a-result-of-a-method/47853774 – Teyyihan Aksu Aug 02 '19 at 12:02
  • @ArturGniewowski i made an answer specifically for this type of problem :) https://stackoverflow.com/questions/57330766/how-to-get-data-from-any-asynchronous-operation-in-android/57330767#57330767, let me know what you think – a_local_nobody Aug 02 '19 at 18:31
0

In order to be able to use the isPlayingFirebase object that is comes from an asynchronous operation, you need to pass it to the playResume() method inside onDataChange() like this:

playResume(isPlayingFirebase);

Now the declaration of your method should look like this:

public void playResume(boolean isPlayingFirebase) { //See the argument
    if (isFirstTimePlayClick) {
        try {
            mediaPlayer = new MediaPlayer();
            mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
            mediaPlayer.setDataSource(myUri);
            mediaPlayer.prepare();
            mediaPlayer.start();
            isFirstTimePlayClick = false;
        } catch (Exception e) {
            Toast.makeText(getApplicationContext(), e.getMessage(), Toast.LENGTH_SHORT).show();
        }

    } else{
        if(mediaPlayer.isPlaying()){
            mediaPlayer.pause();
        }
        else{
            mediaPlayer.start();
        }
    }
}
Alex Mamo
  • 130,605
  • 17
  • 163
  • 193
  • I will try that, thanks. And one more thing, can i handle that situation with interfaces? If i can, can you give me an example? – Teyyihan Aksu Aug 02 '19 at 11:41
  • The most appropriate example is this [How to return DataSnapshot value as a result of a method?](https://stackoverflow.com/questions/47847694/how-to-return-datasnapshot-value-as-a-result-of-a-method/47853774). – Alex Mamo Aug 02 '19 at 11:44
  • Yes, exactly like that ;) – Alex Mamo Aug 02 '19 at 12:01
  • @TeyyihanAksu Keep me posted with your progress ;) – Alex Mamo Aug 02 '19 at 12:04
  • what do you think of this @AlexMamo ? I made an answer specifically for these types of questions, would appreciate any feedback :D https://stackoverflow.com/questions/57330766/how-to-get-data-from-any-asynchronous-operation-in-android/57330767#57330767 – a_local_nobody Aug 02 '19 at 18:32
  • 1
    glad you approve :D i saw now you did do something similar actually, but I couldnt find it when i did some searching initially, so hopefully this helps a few people out – a_local_nobody Aug 02 '19 at 18:42
  • @AlexMamo i handled that with interfaces. It's very helpful. – Teyyihan Aksu Aug 02 '19 at 20:23
  • @TeyyihanAksu Good to hear that ;) – Alex Mamo Aug 02 '19 at 20:44
  • @AlexMamo and also i found another way to deal with these kind of situations. We can implement AsyncTask or Loaders. https://www.youtube.com/watch?v=V60KoFaAQVE&list=PLlyCyjh2pUe9wv-hU4my-Nen_SvXIzxGB&index=39 – Teyyihan Aksu Aug 03 '19 at 14:01
  • @TeyyihanAksu The Firebase Database client, already runs all network operations in a background thread. This means that all operations take place without blocking your main thread. Putting it in an AsyncTask does not give any additional benefits. – Alex Mamo Aug 03 '19 at 14:03