Commit fd4e2120 authored by Geoffrey Métais's avatar Geoffrey Métais
Browse files

Basic Audio player for Android TV

parent 33c91cc8
<?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
......@@ -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);
}
}
}
......@@ -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() {}
}
......@@ -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);
......
/*****************************************************************************
* 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();