[Android] [PATCH 05/11] Basic Audio player for Android TV

Geoffrey Métais geoffrey.metais at gmail.com
Mon Nov 17 17:53:42 CET 2014


---
 vlc-android/AndroidManifest.xml                    |   9 +-
 vlc-android/res/layout/tv_audio_player.xml         |  99 +++++++++
 .../org/videolan/vlc/gui/tv/MainTvActivity.java    | 149 ++++++++-----
 .../vlc/gui/tv/MediaItemDetailsFragment.java       |  71 +++++-
 .../src/org/videolan/vlc/gui/tv/TvUtil.java        |   2 +-
 .../gui/tv/audioplayer/AudioPlayerActivity.java    | 239 +++++++++++++++++++++
 .../gui/tv/audioplayer/DividerItemDecoration.java  | 105 +++++++++
 .../vlc/gui/tv/audioplayer/PlaylistAdapter.java    |  72 +++++++
 8 files changed, 684 insertions(+), 62 deletions(-)
 create mode 100644 vlc-android/res/layout/tv_audio_player.xml
 create mode 100644 vlc-android/src/org/videolan/vlc/gui/tv/audioplayer/AudioPlayerActivity.java
 create mode 100644 vlc-android/src/org/videolan/vlc/gui/tv/audioplayer/DividerItemDecoration.java
 create mode 100644 vlc-android/src/org/videolan/vlc/gui/tv/audioplayer/PlaylistAdapter.java

diff --git a/vlc-android/AndroidManifest.xml b/vlc-android/AndroidManifest.xml
index 3897789..b4fe660 100644
--- a/vlc-android/AndroidManifest.xml
+++ b/vlc-android/AndroidManifest.xml
@@ -31,8 +31,10 @@
 		    android:label="@string/app_name"
 		    android:theme="@style/Theme.Leanback">
 
-		    <intent-filter>
+		    <intent-filter
+		        android:priority="5">
 		      <action android:name="android.intent.action.MAIN" />
+		      <category android:name="android.intent.category.LAUNCHER" />
 		      <category android:name="android.intent.category.LEANBACK_LAUNCHER" />
 		    </intent-filter>
 	  </activity>
@@ -62,6 +64,11 @@
         android:exported="true"
         android:theme="@style/Theme.Leanback"/>
 
+      <activity android:name="org.videolan.vlc.gui.tv.audioplayer.AudioPlayerActivity"
+        android:exported="true"
+        android:launchMode="singleInstance"
+        android:theme="@style/Theme.Leanback"/>
+
         <activity android:name=".gui.CompatErrorActivity" />
         <activity android:name=".gui.PreferencesActivity" />
         <activity
diff --git a/vlc-android/res/layout/tv_audio_player.xml b/vlc-android/res/layout/tv_audio_player.xml
new file mode 100644
index 0000000..06793dc
--- /dev/null
+++ b/vlc-android/res/layout/tv_audio_player.xml
@@ -0,0 +1,99 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" >
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="horizontal"
+        android:weightSum="3.0" >
+
+        <!-- Cover -->
+        <ImageView
+            android:id="@+id/album_cover"
+            android:layout_width="0dip"
+            android:layout_height="match_parent"
+            android:layout_weight="2"
+            android:src="@drawable/background_cone" >
+        </ImageView>
+
+        <!-- Playlist -->
+        <android.support.v7.widget.RecyclerView
+            android:id="@+id/playlist"
+            android:layout_width="0dip"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:scrollbars="vertical" />
+    </LinearLayout>
+
+    <!-- Media HUD -->
+    <RelativeLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_centerHorizontal="true"
+        android:layout_alignParentBottom="true"
+        android:paddingRight="30dip"
+        android:paddingLeft="30dip"
+        android:paddingTop="10dip"
+        android:paddingBottom="5dip"
+        android:background="#33000000">
+        <TextView
+            android:id="@+id/media_title"
+	        android:layout_width="wrap_content"
+	        android:layout_height="wrap_content"
+	        android:layout_centerHorizontal="true"
+	        android:layout_alignParentTop="true"
+	        android:textAppearance="@style/TextAppearance.AppCompat.SearchResult.Title"/>
+        <TextView
+            android:id="@+id/media_artist"
+	        android:layout_width="wrap_content"
+	        android:layout_height="wrap_content"
+	        android:layout_centerHorizontal="true"
+	        android:layout_below="@id/media_title"
+	        android:textAppearance="@style/TextAppearance.AppCompat.SearchResult.Subtitle"/>
+        <ProgressBar
+            android:id="@+id/media_progress"
+	        android:layout_width="wrap_content"
+	        android:layout_height="wrap_content"
+	        style="?android:attr/progressBarStyleHorizontal"
+	        android:layout_centerHorizontal="true"
+	        android:layout_below="@id/media_artist"
+	        android:layout_alignStart="@+id/media_controls"
+	        android:layout_alignEnd="@+id/media_controls"
+	        android:indeterminate="false"/>
+        <!-- Media control buttons -->
+         <LinearLayout
+            android:id="@+id/media_controls"
+	        android:layout_width="wrap_content"
+	        android:layout_height="wrap_content"
+	        android:orientation="horizontal"
+	        android:layout_centerHorizontal="true"
+	        android:layout_below="@id/media_progress">
+	        <ImageView
+	            android:id="@+id/button_previous"
+	            android:layout_width="wrap_content"
+	            android:layout_height="wrap_content"
+	            android:src="@drawable/ic_previous"
+	            android:clickable="true"
+	            android:onClick="onClick"/>
+	        <ImageView
+	            android:id="@+id/button_play"
+	            android:layout_width="wrap_content"
+	            android:layout_height="wrap_content"
+	            android:layout_marginLeft="10dp"
+	            android:layout_marginRight="10dp"
+	            android:src="@drawable/ic_play"
+	            android:clickable="true"
+	            android:onClick="onClick"/>
+	        <ImageView
+	            android:id="@+id/button_next"
+	            android:layout_width="wrap_content"
+	            android:layout_height="wrap_content"
+	            android:src="@drawable/ic_next"
+	            android:clickable="true"
+	            android:onClick="onClick"/>
+	        </LinearLayout>
+    </RelativeLayout>
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/vlc-android/src/org/videolan/vlc/gui/tv/MainTvActivity.java b/vlc-android/src/org/videolan/vlc/gui/tv/MainTvActivity.java
index 1894879..5f8d137 100644
--- a/vlc-android/src/org/videolan/vlc/gui/tv/MainTvActivity.java
+++ b/vlc-android/src/org/videolan/vlc/gui/tv/MainTvActivity.java
@@ -24,15 +24,16 @@ import java.util.HashMap;
 import java.util.concurrent.BrokenBarrierException;
 import java.util.concurrent.CyclicBarrier;
 
+import org.videolan.libvlc.LibVLC;
 import org.videolan.libvlc.Media;
 import org.videolan.vlc.MediaDatabase;
 import org.videolan.vlc.MediaLibrary;
 import org.videolan.vlc.R;
 import org.videolan.vlc.Thumbnailer;
-import org.videolan.vlc.VLCApplication;
+import org.videolan.vlc.gui.audio.AudioUtil;
+import org.videolan.vlc.gui.tv.audioplayer.AudioPlayerActivity;
 import org.videolan.vlc.gui.video.VideoBrowserInterface;
 import org.videolan.vlc.gui.video.VideoListHandler;
-import org.videolan.vlc.gui.video.VideoPlayerActivity;
 import org.videolan.vlc.util.Util;
 
 import android.app.Activity;
@@ -40,9 +41,9 @@ import android.app.FragmentManager;
 import android.content.Intent;
 import android.graphics.Bitmap;
 import android.graphics.drawable.Drawable;
+import android.os.AsyncTask;
 import android.os.Bundle;
 import android.os.Handler;
-import android.os.Parcelable;
 import android.support.v17.leanback.app.BackgroundManager;
 import android.support.v17.leanback.app.BrowseFragment;
 import android.support.v17.leanback.widget.ArrayObjectAdapter;
@@ -57,7 +58,7 @@ import android.view.View.OnClickListener;
 
 public class MainTvActivity extends Activity implements VideoBrowserInterface {
 
-	private static final int NUM_VIDEOS_PREVIEW = 5;
+	private static final int NUM_ITEMS_PREVIEW = 5;
 
 	public static final String TAG = "BrowseActivity";
 
@@ -65,7 +66,7 @@ public class MainTvActivity extends Activity implements VideoBrowserInterface {
 	protected final CyclicBarrier mBarrier = new CyclicBarrier(2);
 	private MediaLibrary mMediaLibrary;
 	private Thumbnailer mThumbnailer;
-	protected Media mItemToUpdate;
+	private Media mItemToUpdate;
 	ArrayObjectAdapter mRowsAdapter;
 	ArrayObjectAdapter videoAdapter;
 	ArrayObjectAdapter audioAdapter;
@@ -91,6 +92,16 @@ public class MainTvActivity extends Activity implements VideoBrowserInterface {
 	@Override
 	protected void onCreate(Bundle savedInstanceState) {
 		super.onCreate(savedInstanceState);
+		/*
+		 * skip browser and show direcly Audio Player if a song is playing
+		 */
+		if (LibVLC.getExistingInstance() != null){
+			if (LibVLC.getExistingInstance().isPlaying()){
+				startActivity(new Intent(this, AudioPlayerActivity.class));
+				finish();
+				return;
+			}
+		}
 		mContext = this;
 		setContentView(R.layout.tv_main_fragment);
 
@@ -162,55 +173,7 @@ public class MainTvActivity extends Activity implements VideoBrowserInterface {
 	}
 
 	public void updateList() {
-		MediaDatabase mediaDatabase = MediaDatabase.getInstance();
-		ArrayList<Media> videoList = mMediaLibrary.getVideoItems();
-		ArrayList<Media> audioList = mMediaLibrary.getAudioItems();
-		int size;
-		Media item;
-		Bitmap picture;
-		mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
-
-		// Update video section
-		if (!videoList.isEmpty()) {
-			size = videoList.size();
-			mVideoIndex = new HashMap<String, Integer>(size);
-			videoAdapter = new ArrayObjectAdapter(
-					new CardPresenter());
-			for (int i = 0 ; i < NUM_VIDEOS_PREVIEW ; ++i) {
-				item = videoList.get(i);
-				picture = mediaDatabase.getPicture(this, item.getLocation());
-
-				videoAdapter.add(item);
-				mVideoIndex.put(item.getLocation(), i);
-				if (mThumbnailer != null){
-					if (picture== null) {
-						mThumbnailer.addJob(item);
-					} else {
-						MediaDatabase.setPicture(item, picture);
-						picture = null;
-					}
-				}
-			}
-			// Empty item to launch grid activity
-			videoAdapter.add(new Media(null, 0, 0, Media.TYPE_GROUP, null, "Browse more", null, null, null, 0, 0, null, 0, 0));
-
-			HeaderItem header = new HeaderItem(HEADER_VIDEO, getString(R.string.video), null);
-			mRowsAdapter.add(new ListRow(header, videoAdapter));
-		}
-		
-		// update audio section
-		if (!audioList.isEmpty()) {
-			audioAdapter = new ArrayObjectAdapter(new CardPresenter());
-			for (Media music : audioList) {
-				audioAdapter.add(music);
-			}
-			// Empty item to launch grid activity
-			audioAdapter.add(new Media(null, 0, 0, Media.TYPE_GROUP, null, "Browse more", null, null, null, 0, 0, null, 0, 0));
-
-			HeaderItem header = new HeaderItem(HEADER_MUSIC, getString(R.string.audio), null);
-			mRowsAdapter.add(new ListRow(header, audioAdapter));
-		}
-		mBrowseFragment.setAdapter(mRowsAdapter);
+		new AsyncUpdate().execute();
 	}
 
 	@Override
@@ -228,4 +191,82 @@ public class MainTvActivity extends Activity implements VideoBrowserInterface {
 	}
 
 	private Handler mHandler = new VideoListHandler(this);
+
+	public class AsyncUpdate extends AsyncTask<Void, Void, Void> {
+
+		public AsyncUpdate() { }
+
+		@Override
+		protected void onPreExecute(){
+			mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
+		}
+		@Override
+		protected Void doInBackground(Void... params) {
+			MediaDatabase mediaDatabase = MediaDatabase.getInstance();
+			ArrayList<Media> videoList = mMediaLibrary.getVideoItems();
+			ArrayList<Media> audioList = mMediaLibrary.getAudioItems();
+			int size;
+			Media item;
+			Bitmap picture;
+
+			// Update video section
+			if (!videoList.isEmpty()) {
+				size = videoList.size();
+				mVideoIndex = new HashMap<String, Integer>(size);
+				videoAdapter = new ArrayObjectAdapter(
+						new CardPresenter());
+				if (NUM_ITEMS_PREVIEW < size)
+					size = NUM_ITEMS_PREVIEW;
+				for (int i = 0 ; i < size ; ++i) {
+					item = videoList.get(i);
+					picture = mediaDatabase.getPicture(mContext, item.getLocation());
+
+					videoAdapter.add(item);
+					mVideoIndex.put(item.getLocation(), i);
+					if (mThumbnailer != null){
+						if (picture== null) {
+							mThumbnailer.addJob(item);
+						} else {
+							MediaDatabase.setPicture(item, picture);
+							picture = null;
+						}
+					}
+				}
+				// Empty item to launch grid activity
+				videoAdapter.add(new Media(null, 0, 0, Media.TYPE_GROUP, null, "Browse more", null, null, null, 0, 0, null, 0, 0));
+
+				HeaderItem header = new HeaderItem(HEADER_VIDEO, getString(R.string.video), null);
+				mRowsAdapter.add(new ListRow(header, videoAdapter));
+			}
+
+			// update audio section
+			if (!audioList.isEmpty()) {
+				size = audioList.size();
+				if (NUM_ITEMS_PREVIEW < size)
+					size = NUM_ITEMS_PREVIEW;
+				audioAdapter = new ArrayObjectAdapter(new CardPresenter());
+				for (int i = 0 ; i < size ; ++i) {
+					item = audioList.get(i);
+					picture = AudioUtil.getCover(mContext, item, 320);
+					if (picture != null){
+						MediaDatabase.setPicture(item, picture);
+						picture = null;
+					}
+					audioAdapter.add(item);
+
+				}
+				// Empty item to launch grid activity
+				audioAdapter.add(new Media(null, 0, 0, Media.TYPE_GROUP, null, "Browse more", null, null, null, 0, 0, null, 0, 0));
+
+				HeaderItem header = new HeaderItem(HEADER_MUSIC, getString(R.string.audio), null);
+				mRowsAdapter.add(new ListRow(header, audioAdapter));
+			}
+			return null;
+		}
+
+		@Override
+		protected void onPostExecute(Void result) {
+			mBrowseFragment.setAdapter(mRowsAdapter);
+		}
+	}
 }
diff --git a/vlc-android/src/org/videolan/vlc/gui/tv/MediaItemDetailsFragment.java b/vlc-android/src/org/videolan/vlc/gui/tv/MediaItemDetailsFragment.java
index 96f6302..5a53b33 100644
--- a/vlc-android/src/org/videolan/vlc/gui/tv/MediaItemDetailsFragment.java
+++ b/vlc-android/src/org/videolan/vlc/gui/tv/MediaItemDetailsFragment.java
@@ -19,9 +19,20 @@
  *****************************************************************************/
 package org.videolan.vlc.gui.tv;
 
+import java.util.ArrayList;
+
+import org.videolan.libvlc.LibVLC;
+import org.videolan.libvlc.LibVlcException;
+import org.videolan.vlc.MediaLibrary;
 import org.videolan.vlc.R;
+import org.videolan.vlc.audio.AudioServiceController;
+import org.videolan.vlc.gui.audio.AudioPlayer;
+import org.videolan.vlc.gui.audio.AudioUtil;
+import org.videolan.vlc.gui.tv.audioplayer.AudioPlayerActivity;
 
+import android.content.Intent;
 import android.content.res.Resources;
+import android.graphics.Bitmap;
 import android.os.Bundle;
 import android.support.v17.leanback.app.DetailsFragment;
 import android.support.v17.leanback.widget.Action;
@@ -32,38 +43,78 @@ import android.support.v17.leanback.widget.DetailsOverviewRowPresenter;
 import android.support.v17.leanback.widget.HeaderItem;
 import android.support.v17.leanback.widget.ListRow;
 import android.support.v17.leanback.widget.ListRowPresenter;
+import android.support.v17.leanback.widget.OnActionClickedListener;
+import android.util.Log;
 
-public class MediaItemDetailsFragment extends DetailsFragment {
+public class MediaItemDetailsFragment extends DetailsFragment implements AudioServiceController.AudioServiceConnectionListener {
 	private static final String TAG = "MediaItemDetailsFragment";
+	private static final int ID_PLAY = 1;
+	private static final int ID_LISTEN = 2;
 	private ArrayObjectAdapter mRowsAdapter;
+    private AudioServiceController mAudioController;
+    private AudioPlayer mAudioPlayer;
+    private MediaItemDetails mMedia;
 
 	@Override
 	public void onCreate(Bundle savedInstanceState) {
 		super.onCreate(savedInstanceState);
 
+        mAudioController = AudioServiceController.getInstance();
 		buildDetails();
 	}
 
+	public void onResume(){
+		super.onResume();
+	}
+
+	public void onPause(){
+		super.onPause();
+		if (mAudioController.isPlaying()){
+			mAudioController.stop();
+			mAudioController.unbindAudioService(getActivity());
+		}
+	}
+
 	private void buildDetails() {
 		Bundle extras = getActivity().getIntent().getExtras();
-		MediaItemDetails media = extras.getParcelable("item");
+		mMedia = extras.getParcelable("item");
 		ClassPresenterSelector selector = new ClassPresenterSelector();
 		// Attach your media item details presenter to the row presenter:
 		DetailsOverviewRowPresenter rowPresenter =
 				new DetailsOverviewRowPresenter(new DetailsDescriptionPresenter());
 
+		rowPresenter.setBackgroundColor(getResources().getColor(R.color.darkorange));
+		rowPresenter.setOnActionClickedListener(new OnActionClickedListener() {
+
+			@Override
+			public void onActionClicked(Action action) {
+				if (action.getId() == ID_LISTEN){
+					mAudioController.bindAudioService(getActivity(), MediaItemDetailsFragment.this);
+				} else if (action.getId() == ID_PLAY){
+					ArrayList<String> locations = new ArrayList<String>();
+					locations.add(mMedia.getLocation());
+					Intent intent = new Intent(getActivity(), AudioPlayerActivity.class);
+					intent.putExtra("locations", locations);
+					startActivity(intent);
+				}
+			}
+		});
 		selector.addClassPresenter(DetailsOverviewRow.class, rowPresenter);
 		selector.addClassPresenter(ListRow.class,
 				new ListRowPresenter());
 		mRowsAdapter = new ArrayObjectAdapter(selector);
 
 		Resources res = getActivity().getResources();
-		DetailsOverviewRow detailsOverview = new DetailsOverviewRow(media);
+		DetailsOverviewRow detailsOverview = new DetailsOverviewRow(mMedia);
 
 		// Add images and action buttons to the details view
-		detailsOverview.setImageDrawable(res.getDrawable(R.drawable.cone));
-		detailsOverview.addAction(new Action(1, "Play"));
-		detailsOverview.addAction(new Action(2, "Delete"));
+		Bitmap cover = AudioUtil.getCover(getActivity(), MediaLibrary.getInstance().getMediaItem(mMedia.getLocation()), 480);
+		if (cover == null)
+			detailsOverview.setImageDrawable(res.getDrawable(R.drawable.cone));
+		else
+			detailsOverview.setImageBitmap(getActivity(), cover);
+		detailsOverview.addAction(new Action(ID_PLAY, "Play"));
+		detailsOverview.addAction(new Action(ID_LISTEN, "Listen"));
 		mRowsAdapter.add(detailsOverview);
 
 		// Add a Related items row
@@ -78,4 +129,12 @@ public class MediaItemDetailsFragment extends DetailsFragment {
 		setAdapter(mRowsAdapter);
 	}
 
+	@Override
+	public void onConnectionSuccess() {
+        mAudioController.load(mMedia.getLocation(), true);
+	}
+
+	@Override
+	public void onConnectionFailed() {}
+
 }
diff --git a/vlc-android/src/org/videolan/vlc/gui/tv/TvUtil.java b/vlc-android/src/org/videolan/vlc/gui/tv/TvUtil.java
index b9d6b20..b75268c 100644
--- a/vlc-android/src/org/videolan/vlc/gui/tv/TvUtil.java
+++ b/vlc-android/src/org/videolan/vlc/gui/tv/TvUtil.java
@@ -18,7 +18,7 @@ public class TvUtil {
 			Intent intent = new Intent(activity,
 					DetailsActivity.class);
 			// pass the item information
-			intent.putExtra("item", (Parcelable)new MediaItemDetails(media.getTitle(), media.getArtist(), media.getAlbum()+"\n"+media.getLocation(), media.getLocation()));
+			intent.putExtra("item", (Parcelable)new MediaItemDetails(media.getTitle(), media.getArtist(), media.getAlbum(), media.getLocation()));
 			activity.startActivity(intent);
 		} else if (media.getType() == Media.TYPE_GROUP){
 			Intent intent = new Intent(activity, VerticalGridActivity.class);
diff --git a/vlc-android/src/org/videolan/vlc/gui/tv/audioplayer/AudioPlayerActivity.java b/vlc-android/src/org/videolan/vlc/gui/tv/audioplayer/AudioPlayerActivity.java
new file mode 100644
index 0000000..6fde7f8
--- /dev/null
+++ b/vlc-android/src/org/videolan/vlc/gui/tv/audioplayer/AudioPlayerActivity.java
@@ -0,0 +1,239 @@
+/*****************************************************************************
+ * AudioPlayerActivity.java
+ *****************************************************************************
+ * Copyright © 2012-2014 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+package org.videolan.vlc.gui.tv.audioplayer;
+
+import java.util.ArrayList;
+
+import org.videolan.libvlc.Media;
+import org.videolan.vlc.MediaLibrary;
+import org.videolan.vlc.R;
+import org.videolan.vlc.audio.AudioServiceController;
+import org.videolan.vlc.gui.audio.AudioUtil;
+import org.videolan.vlc.gui.tv.audioplayer.PlaylistAdapter.ViewHolder;
+import org.videolan.vlc.interfaces.IAudioPlayer;
+import org.videolan.vlc.util.AndroidDevices;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerView.Adapter;
+import android.view.InputDevice;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+public class AudioPlayerActivity extends Activity implements AudioServiceController.AudioServiceConnectionListener, IAudioPlayer{
+	public static final String TAG = "AudioPlayerActivity";
+
+	private Activity mContext;
+    private AudioServiceController mAudioController;
+    private RecyclerView mRecyclerView;
+    private Adapter<ViewHolder> mAdapter;
+    private RecyclerView.LayoutManager mLayoutManager;
+    private ArrayList<String> mLocations;
+
+    //stick event
+    private static final int JOYSTICK_INPUT_DELAY = 300;
+    private long mLastMove;
+
+    private TextView mTitleTv, mArtistTv;
+    private ImageView mPlayPauseButton, mCover;
+    private ProgressBar mProgressBar;
+
+	protected void onCreate(Bundle savedInstanceState) {
+		super.onCreate(savedInstanceState);
+		setContentView(R.layout.tv_audio_player);
+
+		mContext = this;
+		mLocations = getIntent().getStringArrayListExtra("locations");
+		mRecyclerView = (RecyclerView) findViewById(R.id.playlist);
+		mLayoutManager = new LinearLayoutManager(this);
+        mRecyclerView.setLayoutManager(mLayoutManager);
+        mRecyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL_LIST));
+		if (mLocations == null)
+			mLocations = new ArrayList<String>();
+		else {
+			mAdapter = new PlaylistAdapter(mLocations);
+        	mRecyclerView.setAdapter(mAdapter);
+		}
+
+		mAudioController = AudioServiceController.getInstance();
+
+		mTitleTv = (TextView)findViewById(R.id.media_title);
+		mArtistTv = (TextView)findViewById(R.id.media_artist);
+		mPlayPauseButton = (ImageView)findViewById(R.id.button_play);
+		mProgressBar = (ProgressBar)findViewById(R.id.media_progress);
+		mCover = (ImageView)findViewById(R.id.album_cover);
+	}
+
+	public void onStart(){
+		super.onStart();
+		mAudioController.bindAudioService(this, this);
+		mAudioController.addAudioPlayer(this);
+	}
+
+	public void onStop(){
+		super.onStop();
+		mAudioController.removeAudioPlayer(this);
+		mAudioController.unbindAudioService(this);
+		mLocations.clear();
+	}
+
+	@Override
+	public void onConnectionSuccess() {
+		ArrayList<String> medialocations = (ArrayList<String>) mAudioController.getMediaLocations();
+		if (!mLocations.isEmpty() && !mLocations.equals(medialocations))
+			mAudioController.load(mLocations, 0, true);
+		else {
+			mLocations = medialocations;
+			update();
+			mAdapter = new PlaylistAdapter(mLocations);
+			mRecyclerView.setAdapter(mAdapter);
+		}
+	}
+
+	@Override
+	public void onConnectionFailed() {}
+
+	@Override
+	public void update() {
+		mPlayPauseButton.setImageResource(mAudioController.isPlaying() ? R.drawable.ic_pause : R.drawable.ic_play);
+		if (mAudioController.hasMedia()) {
+			mTitleTv.setText(mAudioController.getTitle());
+			mArtistTv.setText(mAudioController.getArtist());
+			mProgressBar.setMax(mAudioController.getLength());
+			Media media = MediaLibrary.getInstance().getMediaItem(mAudioController.getCurrentMediaLocation());
+			Bitmap cover = AudioUtil.getCover(this, media, mCover.getWidth());
+			if (cover == null)
+				cover = mAudioController.getCover();
+			if (cover == null)
+				mCover.setImageResource(R.drawable.background_cone);
+			else
+				mCover.setImageBitmap(cover);
+		}
+	}
+
+	@Override
+	public void updateProgress() {
+		mProgressBar.setProgress(mAudioController.getTime());
+	}
+
+	public boolean onKeyDown(int keyCode, KeyEvent event){
+		switch (keyCode){
+		case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
+        case KeyEvent.KEYCODE_MEDIA_PLAY:
+        case KeyEvent.KEYCODE_MEDIA_PAUSE:
+        case KeyEvent.KEYCODE_SPACE:
+        case KeyEvent.KEYCODE_BUTTON_A:
+			togglePlayPause();
+			break;
+        case KeyEvent.KEYCODE_F:
+        case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
+        case KeyEvent.KEYCODE_BUTTON_R1:
+			goNext();
+			break;
+        case KeyEvent.KEYCODE_R:
+        case KeyEvent.KEYCODE_MEDIA_REWIND:
+        case KeyEvent.KEYCODE_BUTTON_L1:
+			goPrevious();
+			break;
+		default:
+			return super.onKeyDown(keyCode, event);
+		}
+		return true;
+	}
+
+    @TargetApi(12) //only active for Android 3.1+
+    public boolean dispatchGenericMotionEvent(MotionEvent event){
+
+		InputDevice mInputDevice = event.getDevice();
+
+		float x = AndroidDevices.getCenteredAxis(event, mInputDevice,
+				MotionEvent.AXIS_X);
+		float y = AndroidDevices.getCenteredAxis(event, mInputDevice,
+				MotionEvent.AXIS_Y);
+		float z = AndroidDevices.getCenteredAxis(event, mInputDevice,
+				MotionEvent.AXIS_Z);
+		float rz = AndroidDevices.getCenteredAxis(event, mInputDevice,
+				MotionEvent.AXIS_RZ);
+
+		if (System.currentTimeMillis() - mLastMove > JOYSTICK_INPUT_DELAY){
+			if (Math.abs(x) > 0.3){
+				seek(x > 0.0f ? 10000 : -10000);
+				mLastMove = System.currentTimeMillis();
+			} 
+			//TODO Will we change volume in app on TV ?
+			/*else if (Math.abs(rz) > 0.3){
+				mVol = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
+				int delta = -(int) ((rz / 7) * mAudioMax);
+				int vol = (int) Math.min(Math.max(mVol + delta, 0), mAudioMax);
+				setAudioVolume(vol);
+				mLastMove = System.currentTimeMillis();
+			}*/
+		}
+		return true;
+    }
+
+	private void seek(int delta) {
+		int time = mAudioController.getTime()+delta;
+		if (time < 0 || time > mAudioController.getLength())
+			return;
+		mAudioController.setTime(time);
+	}
+
+	public void onClick(View v){
+		switch (v.getId()){
+		case R.id.button_play:
+			togglePlayPause();
+			break;
+		case R.id.button_next:
+			goNext();
+			break;
+		case R.id.button_previous:
+			goPrevious();
+			break;
+		}
+	}
+
+	private void goPrevious() {
+		if (mAudioController.hasPrevious()) {
+			mAudioController.previous();
+		}
+	}
+
+	private void goNext() {
+		if (mAudioController.hasNext()){
+			mAudioController.next();
+		}
+	}
+
+	private void togglePlayPause() {
+		if (mAudioController.isPlaying())
+			mAudioController.pause();
+		else if (mAudioController.hasMedia())
+			mAudioController.play();
+	}
+}
diff --git a/vlc-android/src/org/videolan/vlc/gui/tv/audioplayer/DividerItemDecoration.java b/vlc-android/src/org/videolan/vlc/gui/tv/audioplayer/DividerItemDecoration.java
new file mode 100644
index 0000000..a9c9520
--- /dev/null
+++ b/vlc-android/src/org/videolan/vlc/gui/tv/audioplayer/DividerItemDecoration.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.videolan.vlc.gui.tv.audioplayer;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.view.View;
+
+public class DividerItemDecoration extends RecyclerView.ItemDecoration {
+
+    private static final int[] ATTRS = new int[]{
+            android.R.attr.listDivider
+    };
+
+    public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;
+
+    public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL;
+
+    private Drawable mDivider;
+
+    private int mOrientation;
+
+    public DividerItemDecoration(Context context, int orientation) {
+        final TypedArray a = context.obtainStyledAttributes(ATTRS);
+        mDivider = a.getDrawable(0);
+        a.recycle();
+        setOrientation(orientation);
+    }
+
+    public void setOrientation(int orientation) {
+        if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) {
+            throw new IllegalArgumentException("invalid orientation");
+        }
+        mOrientation = orientation;
+    }
+
+    @Override
+    public void onDraw(Canvas c, RecyclerView parent) {
+        if (mOrientation == VERTICAL_LIST) {
+            drawVertical(c, parent);
+        } else {
+            drawHorizontal(c, parent);
+        }
+    }
+
+    public void drawVertical(Canvas c, RecyclerView parent) {
+        final int left = parent.getPaddingLeft();
+        final int right = parent.getWidth() - parent.getPaddingRight();
+
+        final int childCount = parent.getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            final View child = parent.getChildAt(i);
+            final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
+                    .getLayoutParams();
+            final int top = child.getBottom() + params.bottomMargin;
+            final int bottom = top + mDivider.getIntrinsicHeight();
+            mDivider.setBounds(left, top, right, bottom);
+            mDivider.draw(c);
+        }
+    }
+
+    public void drawHorizontal(Canvas c, RecyclerView parent) {
+        final int top = parent.getPaddingTop();
+        final int bottom = parent.getHeight() - parent.getPaddingBottom();
+
+        final int childCount = parent.getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            final View child = parent.getChildAt(i);
+            final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
+                    .getLayoutParams();
+            final int left = child.getRight() + params.rightMargin;
+            final int right = left + mDivider.getIntrinsicHeight();
+            mDivider.setBounds(left, top, right, bottom);
+            mDivider.draw(c);
+        }
+    }
+
+    @Override
+    public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {
+        if (mOrientation == VERTICAL_LIST) {
+            outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
+        } else {
+            outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
+        }
+    }
+}
diff --git a/vlc-android/src/org/videolan/vlc/gui/tv/audioplayer/PlaylistAdapter.java b/vlc-android/src/org/videolan/vlc/gui/tv/audioplayer/PlaylistAdapter.java
new file mode 100644
index 0000000..0eca16d
--- /dev/null
+++ b/vlc-android/src/org/videolan/vlc/gui/tv/audioplayer/PlaylistAdapter.java
@@ -0,0 +1,72 @@
+/*****************************************************************************
+ * SearchFragment.java
+ *****************************************************************************
+ * Copyright © 2012-2014 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+package org.videolan.vlc.gui.tv.audioplayer;
+
+import java.util.ArrayList;
+
+import org.videolan.libvlc.Media;
+import org.videolan.vlc.MediaLibrary;
+
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+public class PlaylistAdapter extends RecyclerView.Adapter<PlaylistAdapter.ViewHolder> {
+    private ArrayList<String> mDataset;
+	private static MediaLibrary sMediaLibrary = MediaLibrary.getInstance();
+
+    public static class ViewHolder extends RecyclerView.ViewHolder {
+        public TextView mTitleTv;
+        public TextView mArtistTv;
+        public ViewHolder(View v) {
+            super(v);
+            mTitleTv = (TextView) v.findViewById(android.R.id.text1);
+            mArtistTv = (TextView) v.findViewById(android.R.id.text2);
+        }
+    }
+
+    public PlaylistAdapter(ArrayList<String> myDataset) {
+        mDataset = myDataset;
+    }
+
+    @Override
+    public PlaylistAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
+                                                   int viewType) {
+        View v = LayoutInflater.from(parent.getContext())
+                               .inflate(android.R.layout.simple_list_item_2, parent, false);
+
+        ViewHolder vh = new ViewHolder(v);
+        return vh;
+    }
+
+    @Override
+    public void onBindViewHolder(ViewHolder holder, int position) {
+        Media media = sMediaLibrary.getMediaItem(mDataset.get(position));
+        holder.mTitleTv.setText(media.getTitle());
+        holder.mArtistTv.setText(media.getArtist());
+    }
+
+    @Override
+    public int getItemCount() {
+        return mDataset.size();
+    }
+}
\ No newline at end of file
-- 
1.9.1



More information about the Android mailing list