[Android] [PATCH 1/2] Playlist files support

Geoffrey Métais geoffrey.metais at gmail.com
Mon Mar 23 16:51:44 CET 2015


m3u, asx, b4s, pls and xspf for now
---
 .../src/org/videolan/libvlc/util/Extensions.java   | 24 +++---
 vlc-android/res/layout/audio_browser.xml           | 20 ++++-
 vlc-android/res/values/strings.xml                 |  1 +
 vlc-android/src/org/videolan/vlc/MediaLibrary.java | 16 +++-
 vlc-android/src/org/videolan/vlc/MediaWrapper.java |  3 +
 .../vlc/gui/audio/AudioBrowserFragment.java        | 93 +++++++++++++++++++---
 .../vlc/gui/audio/AudioBrowserListAdapter.java     |  5 ++
 7 files changed, 133 insertions(+), 29 deletions(-)

diff --git a/libvlc/src/org/videolan/libvlc/util/Extensions.java b/libvlc/src/org/videolan/libvlc/util/Extensions.java
index e641a63..441d558 100644
--- a/libvlc/src/org/videolan/libvlc/util/Extensions.java
+++ b/libvlc/src/org/videolan/libvlc/util/Extensions.java
@@ -20,13 +20,16 @@
 
 package org.videolan.libvlc.util;
 
+import java.util.Arrays;
 import java.util.HashSet;
 
 public class Extensions {
 
-    public final static HashSet<String> VIDEO;
-    public final static HashSet<String> AUDIO;
-    public final static HashSet<String> SUBTITLES;
+    public final static HashSet<String> VIDEO = new HashSet<String>();
+    public final static HashSet<String> AUDIO = new HashSet<String>();
+    public final static HashSet<String> SUBTITLES = new HashSet<String>();
+    public final static HashSet<String> PLAYLIST = new HashSet<String>();
+
 
     static {
         final String[] videoExtensions = {
@@ -49,14 +52,11 @@ public class Extensions {
                 ".rt",   ".aqt", ".txt", ".usf", ".jss",  ".cdg", ".psb", ".mpsub",".mpl2",
                 ".pjs", ".dks", ".stl", ".vtt" };
 
-        VIDEO = new HashSet<String>();
-        for (String item : videoExtensions)
-            VIDEO.add(item);
-        AUDIO = new HashSet<String>();
-        for (String item : audioExtensions)
-            AUDIO.add(item);
-        SUBTITLES = new HashSet<String>();
-        for (String item : subtitlesExtensions)
-            SUBTITLES.add(item);
+        final String[] playlistExtensions = {".m3u", ".asx",  ".b4s",  ".pls", ".xspf"/*,  ".zip"*/};
+
+        VIDEO.addAll(Arrays.asList(videoExtensions));
+        AUDIO.addAll(Arrays.asList(audioExtensions));
+        SUBTITLES.addAll(Arrays.asList(subtitlesExtensions));
+        PLAYLIST.addAll(Arrays.asList(playlistExtensions));
     }
 }
\ No newline at end of file
diff --git a/vlc-android/res/layout/audio_browser.xml b/vlc-android/res/layout/audio_browser.xml
index 2621331..9c35bf8 100644
--- a/vlc-android/res/layout/audio_browser.xml
+++ b/vlc-android/res/layout/audio_browser.xml
@@ -11,8 +11,8 @@
 
     <TextView
         android:id="@+id/no_media"
-        android:layout_width="fill_parent"
-        android:layout_height="wrap_content"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
         android:layout_below="@+id/header"
         android:gravity="center"
         android:text="@string/nomedia"
@@ -85,7 +85,21 @@
                 android:nextFocusUp="@id/header"
                 android:nextFocusDown="@id/header"
                 android:nextFocusLeft="@id/songs_list"
-                android:nextFocusRight="@+id/genres_list" />
+                android:nextFocusRight="@+id/playlists_list" />
+            <ListView
+                android:id="@+id/playlists_list"
+                android:layout_width="fill_parent"
+                android:layout_height="fill_parent"
+                android:paddingLeft="20dp"
+                android:paddingRight="20dp"
+                android:fastScrollEnabled="true"
+                android:paddingBottom="@dimen/listview_bottom_padding"
+                android:clipToPadding="false"
+                android:focusable="true"
+                android:nextFocusUp="@id/header"
+                android:nextFocusDown="@id/header"
+                android:nextFocusLeft="@id/genres_list"
+                android:nextFocusRight="@+id/playlists_list" />
         </android.support.v4.view.ViewPager>
     </org.videolan.vlc.widget.SwipeRefreshLayout>
 
diff --git a/vlc-android/res/values/strings.xml b/vlc-android/res/values/strings.xml
index 7cf383e..1675b38 100644
--- a/vlc-android/res/values/strings.xml
+++ b/vlc-android/res/values/strings.xml
@@ -82,6 +82,7 @@
     <string name="loading">Loading</string>
     <string name="please_wait">Please wait…</string>
     <string name="nomedia">No media files found, please transfer some files to your device or adjust your preferences.</string>
+    <string name="noplaylist">No playlist found.</string>
     <string name="mediafiles">Media files</string>
     <string name="notavailable">not available</string>
     <string name="nosubdirectory">No subdirectories.</string>
diff --git a/vlc-android/src/org/videolan/vlc/MediaLibrary.java b/vlc-android/src/org/videolan/vlc/MediaLibrary.java
index bd277d1..6d3ce3b 100644
--- a/vlc-android/src/org/videolan/vlc/MediaLibrary.java
+++ b/vlc-android/src/org/videolan/vlc/MediaLibrary.java
@@ -172,6 +172,19 @@ public class MediaLibrary {
         return audioItems;
     }
 
+    public ArrayList<MediaWrapper> getPlaylistItems() {
+        ArrayList<MediaWrapper> playlistItems = new ArrayList<MediaWrapper>();
+        mItemListLock.readLock().lock();
+        for (int i = 0; i < mItemList.size(); i++) {
+            MediaWrapper item = mItemList.get(i);
+            if (item.getType() == MediaWrapper.TYPE_PLAYLIST) {
+                playlistItems.add(item);
+            }
+        }
+        mItemListLock.readLock().unlock();
+        return playlistItems;
+    }
+
     public ArrayList<MediaWrapper> getMediaItems() {
         return mItemList;
     }
@@ -415,7 +428,8 @@ public class MediaLibrary {
                     if (dotIndex != -1) {
                         String fileExt = fileName.substring(dotIndex);
                         accepted = Extensions.AUDIO.contains(fileExt) ||
-                                   Extensions.VIDEO.contains(fileExt);
+                                   Extensions.VIDEO.contains(fileExt) ||
+                                   Extensions.PLAYLIST.contains(fileExt);
                     }
                 }
             }
diff --git a/vlc-android/src/org/videolan/vlc/MediaWrapper.java b/vlc-android/src/org/videolan/vlc/MediaWrapper.java
index fc38cb7..7430db4 100644
--- a/vlc-android/src/org/videolan/vlc/MediaWrapper.java
+++ b/vlc-android/src/org/videolan/vlc/MediaWrapper.java
@@ -44,6 +44,7 @@ public class MediaWrapper implements Parcelable {
     public final static int TYPE_GROUP = 2;
     public final static int TYPE_DIR = 3;
     public final static int TYPE_SUBTITLE = 4;
+    public final static int TYPE_PLAYLIST = 5;
 
     protected String mTitle;
     private String mArtist;
@@ -134,6 +135,8 @@ public class MediaWrapper implements Parcelable {
                     mType = TYPE_AUDIO;
                 } else if (Extensions.SUBTITLES.contains(fileExt)) {
                     mType = TYPE_SUBTITLE;
+                } else if (Extensions.PLAYLIST.contains(fileExt)) {
+                    mType = TYPE_PLAYLIST;
                 }
             }
             if (mType == TYPE_ALL) {
diff --git a/vlc-android/src/org/videolan/vlc/gui/audio/AudioBrowserFragment.java b/vlc-android/src/org/videolan/vlc/gui/audio/AudioBrowserFragment.java
index 5455faa..ab1bb53 100644
--- a/vlc-android/src/org/videolan/vlc/gui/audio/AudioBrowserFragment.java
+++ b/vlc-android/src/org/videolan/vlc/gui/audio/AudioBrowserFragment.java
@@ -48,10 +48,14 @@ import android.widget.ImageView;
 import android.widget.ListView;
 import android.widget.PopupMenu;
 import android.widget.PopupMenu.OnMenuItemClickListener;
+import android.widget.TextView;
 
 import com.android.widget.SlidingTabLayout;
 
+import org.videolan.libvlc.LibVLC;
 import org.videolan.libvlc.LibVlcUtil;
+import org.videolan.libvlc.Media;
+import org.videolan.libvlc.util.MediaBrowser;
 import org.videolan.vlc.MediaLibrary;
 import org.videolan.vlc.MediaWrapper;
 import org.videolan.vlc.R;
@@ -75,22 +79,24 @@ import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 
-public class AudioBrowserFragment extends BrowserFragment implements SwipeRefreshLayout.OnRefreshListener, SlidingTabLayout.OnTabChangedListener {
+public class AudioBrowserFragment extends BrowserFragment implements SwipeRefreshLayout.OnRefreshListener, SlidingTabLayout.OnTabChangedListener, MediaBrowser.EventListener {
     public final static String TAG = "VLC/AudioBrowserFragment";
 
     private AudioServiceController mAudioController;
     private MediaLibrary mMediaLibrary;
+    private MediaBrowser mMediaBrowser;
 
     List<MediaWrapper> mAudioList;
     private AudioBrowserListAdapter mArtistsAdapter;
     private AudioBrowserListAdapter mAlbumsAdapter;
     private AudioBrowserListAdapter mSongsAdapter;
     private AudioBrowserListAdapter mGenresAdapter;
+    private AudioBrowserListAdapter mPlaylistAdapter;
     private ConcurrentLinkedQueue<AudioBrowserListAdapter> mAdaptersToNotify = new ConcurrentLinkedQueue<AudioBrowserListAdapter>();
 
     private ViewPager mViewPager;
     private SlidingTabLayout mSlidingTabLayout;
-    private View mEmptyView;
+    private TextView mEmptyView;
     private List<View> mLists;
     private ImageView mFabPlayShuffleAll;
 
@@ -98,7 +104,8 @@ public class AudioBrowserFragment extends BrowserFragment implements SwipeRefres
     public final static int MODE_ALBUM = 1;
     public final static int MODE_SONG = 2;
     public final static int MODE_GENRE = 3;
-    public final static int MODE_TOTAL = 4; // Number of audio browser modes
+    public final static int MODE_PLAYLIST = 4;
+    public final static int MODE_TOTAL = 5; // Number of audio browser modes
 
     public final static int MSG_LOADING = 0;
     private volatile boolean mDisplaying = false;
@@ -118,11 +125,13 @@ public class AudioBrowserFragment extends BrowserFragment implements SwipeRefres
         mArtistsAdapter = new AudioBrowserListAdapter(getActivity(), AudioBrowserListAdapter.ITEM_WITH_COVER);
         mAlbumsAdapter = new AudioBrowserListAdapter(getActivity(), AudioBrowserListAdapter.ITEM_WITH_COVER);
         mGenresAdapter = new AudioBrowserListAdapter(getActivity(), AudioBrowserListAdapter.ITEM_WITHOUT_COVER);
+        mPlaylistAdapter = new AudioBrowserListAdapter(getActivity(), AudioBrowserListAdapter.ITEM_WITHOUT_COVER);
 
         mSongsAdapter.setContextPopupMenuListener(mContextPopupMenuListener);
         mArtistsAdapter.setContextPopupMenuListener(mContextPopupMenuListener);
         mAlbumsAdapter.setContextPopupMenuListener(mContextPopupMenuListener);
         mGenresAdapter.setContextPopupMenuListener(mContextPopupMenuListener);
+        mPlaylistAdapter.setContextPopupMenuListener(mContextPopupMenuListener);
     }
 
     @Override
@@ -130,21 +139,23 @@ public class AudioBrowserFragment extends BrowserFragment implements SwipeRefres
 
         View v = inflater.inflate(R.layout.audio_browser, container, false);
 
-        mEmptyView = v.findViewById(R.id.no_media);
+        mEmptyView = (TextView) v.findViewById(R.id.no_media);
 
         ListView songsList = (ListView)v.findViewById(R.id.songs_list);
         ListView artistList = (ListView)v.findViewById(R.id.artists_list);
         ListView albumList = (ListView)v.findViewById(R.id.albums_list);
         ListView genreList = (ListView)v.findViewById(R.id.genres_list);
+        ListView playlistsList = (ListView)v.findViewById(R.id.playlists_list);
 
         songsList.setAdapter(mSongsAdapter);
         artistList.setAdapter(mArtistsAdapter);
         albumList.setAdapter(mAlbumsAdapter);
         genreList.setAdapter(mGenresAdapter);
+        playlistsList.setAdapter(mPlaylistAdapter);
 
-        mLists = Arrays.asList((View)artistList, albumList, songsList, genreList);
+        mLists = Arrays.asList((View)artistList, albumList, songsList, genreList, playlistsList);
         String[] titles = new String[] {getString(R.string.artists), getString(R.string.albums),
-                getString(R.string.songs), getString(R.string.genres)};
+                getString(R.string.songs), getString(R.string.genres), getString(R.string.playlists)};
         mViewPager = (ViewPager) v.findViewById(R.id.pager);
         mViewPager.setOffscreenPageLimit(MODE_TOTAL - 1);
         mViewPager.setAdapter(new AudioPagerAdapter(mLists, titles));
@@ -159,16 +170,19 @@ public class AudioBrowserFragment extends BrowserFragment implements SwipeRefres
         artistList.setOnItemClickListener(artistListListener);
         albumList.setOnItemClickListener(albumListListener);
         genreList.setOnItemClickListener(genreListListener);
+        playlistsList.setOnItemClickListener(playlistListener);
 
         artistList.setOnKeyListener(keyListener);
         albumList.setOnKeyListener(keyListener);
         songsList.setOnKeyListener(keyListener);
         genreList.setOnKeyListener(keyListener);
+        playlistsList.setOnKeyListener(keyListener);
 
         registerForContextMenu(songsList);
         registerForContextMenu(artistList);
         registerForContextMenu(albumList);
         registerForContextMenu(genreList);
+        registerForContextMenu(playlistsList);
 
         mSwipeRefreshLayout = (SwipeRefreshLayout) v.findViewById(R.id.swipeLayout);
 
@@ -179,6 +193,7 @@ public class AudioBrowserFragment extends BrowserFragment implements SwipeRefres
         artistList.setOnScrollListener(mScrollListener);
         albumList.setOnScrollListener(mScrollListener);
         genreList.setOnScrollListener(mScrollListener);
+        playlistsList.setOnScrollListener(mScrollListener);
 
         mFabPlayShuffleAll = (ImageView) v.findViewById(R.id.fab_play_shuffle_all);
         mFabPlayShuffleAll.setOnClickListener(new View.OnClickListener() {
@@ -212,6 +227,10 @@ public class AudioBrowserFragment extends BrowserFragment implements SwipeRefres
     public void onPause() {
         super.onPause();
         mMediaLibrary.removeUpdateHandler(mHandler);
+        if (mMediaBrowser != null) {
+            mMediaBrowser.release();
+            mMediaBrowser = null;
+        }
     }
 
     @Override
@@ -330,6 +349,14 @@ public class AudioBrowserFragment extends BrowserFragment implements SwipeRefres
         }
     };
 
+    OnItemClickListener playlistListener = new OnItemClickListener() {
+        @Override
+        public void onItemClick(AdapterView<?> av, View v, int p, long id) {
+            String mediaLocation = mPlaylistAdapter.getItem(p).mMediaList.get(0).getLocation();
+            mAudioController.load(mediaLocation, true);
+        }
+    };
+
     @Override
     public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
         MenuInflater inflater = getActivity().getMenuInflater();
@@ -424,6 +451,11 @@ public class AudioBrowserFragment extends BrowserFragment implements SwipeRefres
                 case MODE_GENRE:
                     medias = mGenresAdapter.getLocations(groupPosition);
                     break;
+                case MODE_PLAYLIST: //For playlist, we browse tracks with mediabrowser, and add them in callbacks onMediaAdded and onBrowseEnd
+                    if (mMediaBrowser == null)
+                        mMediaBrowser = new MediaBrowser(LibVLC.getInstance(), this);
+                    mMediaBrowser.browse(mPlaylistAdapter.getMedia(groupPosition).get(0).getLocation());
+                    return true;
                 default:
                     return true;
             }
@@ -506,6 +538,7 @@ public class AudioBrowserFragment extends BrowserFragment implements SwipeRefres
                     mHandler.removeMessages(MSG_LOADING);
                     mSwipeRefreshLayout.setRefreshing(false);
                     mDisplaying = false;
+                    updateEmptyView(mViewPager.getCurrentItem());
                 }
             });
     }
@@ -517,9 +550,35 @@ public class AudioBrowserFragment extends BrowserFragment implements SwipeRefres
 
     @Override
     public void tabChanged(int position) {
+        updateEmptyView(position);
         setFabPlayShuffleAllVisibility();
     }
 
+    private void updateEmptyView(int position) {
+        if (position == MODE_PLAYLIST){
+            mEmptyView.setVisibility(mPlaylistAdapter.isEmpty() ? View.VISIBLE : View.GONE);
+            mEmptyView.setText(R.string.noplaylist);
+        } else {
+            mEmptyView.setVisibility(mAudioList.isEmpty() ? View.VISIBLE : View.GONE);
+            mEmptyView.setText(R.string.nomedia);
+        }
+    }
+
+    ArrayList<String> mLocationsToAppend = new ArrayList<String>(); //Playlist tracks to append
+
+    @Override
+    public void onMediaAdded(int index, Media media) {
+        mLocationsToAppend.add(media.getMrl());
+    }
+
+    @Override
+    public void onMediaRemoved(int index, Media media) {}
+
+    @Override
+    public void onBrowseEnd() {
+        mAudioController.append(mLocationsToAppend);
+    }
+
     private static class AudioBrowserHandler extends WeakHandler<AudioBrowserFragment> {
         public AudioBrowserHandler(AudioBrowserFragment owner) {
             super(owner);
@@ -543,24 +602,20 @@ public class AudioBrowserFragment extends BrowserFragment implements SwipeRefres
     };
 
     private void updateLists() {
-        mSongsAdapter.clear();
-        mArtistsAdapter.clear();
-        mAlbumsAdapter.clear();
-        mGenresAdapter.clear();
+        clear();
         mAudioList = MediaLibrary.getInstance().getAudioItems();
         if (mAudioList.isEmpty()){
+            updateEmptyView(mViewPager.getCurrentItem());
             mSwipeRefreshLayout.setRefreshing(false);
-            mEmptyView.setVisibility(View.VISIBLE);
             mSlidingTabLayout.setVisibility(View.GONE);
             focusHelper(true, R.id.artists_list);
         } else {
             mSlidingTabLayout.setVisibility(View.VISIBLE);
-            mEmptyView.setVisibility(View.GONE);
             mHandler.sendEmptyMessageDelayed(MSG_LOADING, 300);
 
             ExecutorService tpe = Executors.newSingleThreadExecutor();
             ArrayList<Runnable> tasks = new ArrayList<Runnable>(Arrays.asList(updateArtists,
-                    updateAlbums, updateSongs, updateGenres));
+                    updateAlbums, updateSongs, updateGenres, updatePlaylists));
 
             //process the visible list first
             tasks.add(0, tasks.remove(mViewPager.getCurrentItem()));
@@ -620,6 +675,17 @@ public class AudioBrowserFragment extends BrowserFragment implements SwipeRefres
         }
     };
 
+    Runnable updatePlaylists = new Runnable() {
+        @Override
+        public void run() {
+            ArrayList<MediaWrapper> playlists = mMediaLibrary.getPlaylistItems();
+            mPlaylistAdapter.addAll(playlists, AudioBrowserListAdapter.TYPE_PLAYLISTS);
+            mAdaptersToNotify.add(mPlaylistAdapter);
+            if (mReadyToDisplay && !mDisplaying)
+                display();
+        }
+    };
+
     AudioBrowserListAdapter.ContextPopupMenuListener mContextPopupMenuListener
         = new AudioBrowserListAdapter.ContextPopupMenuListener() {
 
@@ -668,5 +734,6 @@ public class AudioBrowserFragment extends BrowserFragment implements SwipeRefres
         mArtistsAdapter.clear();
         mAlbumsAdapter.clear();
         mSongsAdapter.clear();
+        mPlaylistAdapter.clear();
     }
 }
diff --git a/vlc-android/src/org/videolan/vlc/gui/audio/AudioBrowserListAdapter.java b/vlc-android/src/org/videolan/vlc/gui/audio/AudioBrowserListAdapter.java
index be1703b..33bc575 100644
--- a/vlc-android/src/org/videolan/vlc/gui/audio/AudioBrowserListAdapter.java
+++ b/vlc-android/src/org/videolan/vlc/gui/audio/AudioBrowserListAdapter.java
@@ -57,6 +57,7 @@ public class AudioBrowserListAdapter extends BaseAdapter implements SectionIndex
     public final static int TYPE_ALBUMS = 1;
     public final static int TYPE_SONGS = 2;
     public final static int TYPE_GENRES = 3;
+    public final static int TYPE_PLAYLISTS = 4;
 
     // Key: the item title, value: ListItem of only media item (no separator).
     private Map<String, ListItem> mMediaItemMap;
@@ -145,6 +146,10 @@ public class AudioBrowserListAdapter extends BaseAdapter implements SectionIndex
                             title = Util.getMediaGenre(mContext, media);
                             subTitle = null;
                             break;
+                        case TYPE_PLAYLISTS:
+                            title = media.getTitle();
+                            subTitle = null;
+                            break;
                         case TYPE_SONGS:
                         default:
                             title = media.getTitle();
-- 
2.1.0



More information about the Android mailing list