简体   繁体   中英

How to use Firebase with the Android universal music player?

Here is the Google sample app. It's set up to pull metadata from a URL with a JSON. I would like to know how to have Firebase be my source.

Here is my attempt in changing the RemoteJSONSource class:

package com.mm.android.uamp.model;

import android.support.v4.media.MediaMetadataCompat;
import android.util.Log;

import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.database.ValueEventListener;
import com.mm.android.uamp.utils.LogHelper;

import java.util.ArrayList;
import java.util.Iterator;

public class RemoteJSONSource implements MusicProviderSource {

private static final String TAG = LogHelper.makeLogTag(RemoteJSONSource.class);

DatabaseReference mRootRef = FirebaseDatabase.getInstance().getReference();
DatabaseReference mMusic = mRootRef.child("music");

ArrayList<MediaMetadataCompat> tracksFromFB = new ArrayList<>();
public void buildFromFirebase(){

    mMusic.addListenerForSingleValueEvent(new ValueEventListener() {

        @Override
        public void onDataChange(DataSnapshot dataSnapshot) {

            for (DataSnapshot music : dataSnapshot.getChildren()){

                String title = music.child("title").getValue(String.class);
                String album = music.child("album").getValue(String.class);
                String artist = music.child("artist").getValue(String.class);
                String genre = music.child("genre").getValue(String.class);
                String source = music.child("source").getValue(String.class);
                String id = String.valueOf(source.hashCode());
                String iconUrl = music.child("image").getValue(String.class);
                int trackNumber = music.child("trackNumber").getValue(Integer.class);
                int totalTrackCount = music.child("totalTrackCount").getValue(Integer.class);
                int duration = music.child("duration").getValue(Integer.class);

                MediaMetadataCompat theMetadataFB = new MediaMetadataCompat.Builder()
                        .putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, id)
                        .putString(MusicProviderSource.CUSTOM_METADATA_TRACK_SOURCE, source)
                        .putString(MediaMetadataCompat.METADATA_KEY_ALBUM, album)
                        .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, artist)
                        .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, duration)
                        .putString(MediaMetadataCompat.METADATA_KEY_GENRE, genre)
                        .putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, iconUrl)
                        .putString(MediaMetadataCompat.METADATA_KEY_TITLE, title)
                        .putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, trackNumber)
                        .putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, totalTrackCount)
                        .build();

                tracksFromFB.add(theMetadataFB);
            }
        }

        @Override
        public void onCancelled(DatabaseError databaseError) {
            Log.w(TAG, "loadPost:onCancelled", databaseError.toException());
        }
    });
}

@Override
public Iterator<MediaMetadataCompat> iterator() {
    buildFromFirebase();
    ArrayList<MediaMetadataCompat> tracksFB = tracksFromFB;

    return tracksFB.iterator();
}

}

The firebase onDataChange is asynchronous so I think it hasn't finished pulling the data yet before the iterator method returns tracksFB.iterator cause tracksFB array is null. Weird thing is when I run in debug mode with a line break on

ArrayList tracksFB = tracksFromFB;

It works. From my research I think I need a callback or some type of pausing task, but I just cant figure it out.

Possible relevant code connected to the iterator method

public interface MusicProviderSource {
   String CUSTOM_METADATA_TRACK_SOURCE = "__SOURCE__";
   Iterator<MediaMetadataCompat> iterator();
}

next

public class MusicProvider {
private static final String TAG = LogHelper.makeLogTag(MusicProvider.class);
private MusicProviderSource mSource;
private ConcurrentMap<String, List<MediaMetadataCompat>> mMusicListByGenre;
private final ConcurrentMap<String, MutableMediaMetadata> mMusicListById;

private final Set<String> mFavoriteTracks;

enum State {
    NON_INITIALIZED, INITIALIZING, INITIALIZED
}

private volatile State mCurrentState = State.NON_INITIALIZED;

public interface Callback {
    void onMusicCatalogReady(boolean success);
}

public MusicProvider() {
    this(new RemoteJSONSource());
}
public MusicProvider(MusicProviderSource source) {
    mSource = source;
    mMusicListByGenre = new ConcurrentHashMap<>();
    mMusicListById = new ConcurrentHashMap<>();
    mFavoriteTracks = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());
}

public Iterable<String> getGenres() {
    if (mCurrentState != State.INITIALIZED) {
        return Collections.emptyList();
    }
    return mMusicListByGenre.keySet();
}

/**
 * Get an iterator over a shuffled collection of all songs
 */
public Iterable<MediaMetadataCompat> getShuffledMusic() {
    if (mCurrentState != State.INITIALIZED) {
        return Collections.emptyList();
    }
    List<MediaMetadataCompat> shuffled = new ArrayList<>(mMusicListById.size());
    for (MutableMediaMetadata mutableMetadata: mMusicListById.values()) {
        shuffled.add(mutableMetadata.metadata);
    }
    Collections.shuffle(shuffled);
    return shuffled;
}

/**
 * Get music tracks of the given genre
 *
 */
public Iterable<MediaMetadataCompat> getMusicsByGenre(String genre) {
    if (mCurrentState != State.INITIALIZED || !mMusicListByGenre.containsKey(genre)) {
        return Collections.emptyList();
    }
    return mMusicListByGenre.get(genre);
}

}

Also the musicService.java in the link above might be relevant. PLEASE help!

There are two ways I can think to do this, but I'm not familiar enough with Firebase to provide working code.

The sample executes iterator() in an AsyncTask , expecting it to block until it can provide a response. So the first, and probably easiest, way to fix it would be to cause iterator() to wait on the data being loaded, or it failing to load. This could be a spinlock or something like wait/notify.

if (!dataloaded) {
    try {
        wait();
    } catch (InterruptedException e) {}    
}
ArrayList<MediaMetadataCompat> tracksFB = tracksFromFB;

return tracksFB.iterator();

I'd call buildFromFirebase(); in the constructor though, rather than waiting.

The second option would be to refactor UAMP to have it load the catalog asynchronously. This would be a lot more work, but it may result in a better design in the long run.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM