[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