/*****************************************************************************
* VideoListActivity.java
*****************************************************************************
* Copyright © 2011-2012 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.video;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import org.videolan.android.ui.SherlockGridFragment;
import org.videolan.libvlc.Media;
import org.videolan.vlc.AudioServiceController;
import org.videolan.vlc.MediaDatabase;
import org.videolan.vlc.MediaGroup;
import org.videolan.vlc.MediaLibrary;
import org.videolan.vlc.Thumbnailer;
import org.videolan.vlc.Util;
import org.videolan.vlc.VlcRunnable;
import org.videolan.vlc.WeakHandler;
import org.videolan.vlc.gui.CommonDialogs;
import org.videolan.vlc.gui.MainActivity;
import org.videolan.vlc.interfaces.ISortable;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Configuration;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v4.app.FragmentActivity;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.LayoutInflater;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.GridView;
import android.widget.LinearLayout;
import android.widget.PopupMenu;
import android.widget.PopupMenu.OnMenuItemClickListener;
import android.widget.TextView;
import org.tribler.triblersvod.gui.R;
public class VideoGridFragment extends SherlockGridFragment implements ISortable {
public final static String TAG = "VLC/VideoListFragment";
protected static final String ACTION_SCAN_START = "org.videolan.vlc.gui.ScanStart";
protected static final String ACTION_SCAN_STOP = "org.videolan.vlc.gui.ScanStop";
protected static final int UPDATE_ITEM = 0;
/* Constants used to switch from Grid to List and vice versa */
//FIXME If you know a way to do this in pure XML please do it!
private static final int GRID_ITEM_WIDTH_DP = 156;
private static final int GRID_HORIZONTAL_SPACING_DP = 20;
private static final int GRID_VERTICAL_SPACING_DP = 20;
private static final int GRID_STRETCH_MODE = GridView.STRETCH_COLUMN_WIDTH;
private static final int LIST_HORIZONTAL_SPACING_DP = 0;
private static final int LIST_VERTICAL_SPACING_DP = 10;
private static final int LIST_STRETCH_MODE = GridView.STRETCH_COLUMN_WIDTH;
protected LinearLayout mLayoutFlipperLoading;
protected TextView mTextViewNomedia;
protected Media mItemToUpdate;
protected String mGroup;
protected final CyclicBarrier mBarrier = new CyclicBarrier(2);
private VideoListAdapter mVideoAdapter;
private MediaLibrary mMediaLibrary;
private Thumbnailer mThumbnailer;
private VideoGridAnimator mAnimator;
/* All subclasses of Fragment must include a public empty constructor. */
public VideoGridFragment() { }
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mVideoAdapter = new VideoListAdapter(getActivity(), this);
mMediaLibrary = MediaLibrary.getInstance(getActivity());
setListAdapter(mVideoAdapter);
/* Load the thumbnailer */
FragmentActivity activity = getActivity();
if (activity != null)
mThumbnailer = new Thumbnailer(activity, activity.getWindowManager().getDefaultDisplay());
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
getSherlockActivity().getSupportActionBar().setTitle(R.string.video);
View v = inflater.inflate(R.layout.video_grid, container, false);
// init the information for the scan (1/2)
mLayoutFlipperLoading = (LinearLayout) v.findViewById(R.id.layout_flipper_loading);
mTextViewNomedia = (TextView) v.findViewById(R.id.textview_nomedia);
return v;
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
registerForContextMenu(getGridView());
// init the information for the scan (2/2)
IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_SCAN_START);
filter.addAction(ACTION_SCAN_STOP);
getActivity().registerReceiver(messageReceiverVideoListFragment, filter);
Log.i(TAG,"mMediaLibrary.isWorking() " + Boolean.toString(mMediaLibrary.isWorking()));
if (mMediaLibrary.isWorking()) {
actionScanStart(getActivity().getApplicationContext());
}
mAnimator = new VideoGridAnimator(getGridView());
}
@Override
public void onPause() {
super.onPause();
mMediaLibrary.removeUpdateHandler(mHandler);
/* Stop the thumbnailer */
if (mThumbnailer != null)
mThumbnailer.stop();
}
@Override
public void onResume() {
super.onResume();
//Get & set times
HashMap<String, Long> times = MediaDatabase.getInstance(getActivity()).getVideoTimes(getActivity());
mVideoAdapter.setTimes(times);
mVideoAdapter.notifyDataSetChanged();
updateList();
mMediaLibrary.addUpdateHandler(mHandler);
updateViewMode();
mAnimator.animate();
/* Start the thumbnailer */
if (mThumbnailer != null)
mThumbnailer.start(this);
}
@Override
public void onDestroyView() {
getActivity().unregisterReceiver(messageReceiverVideoListFragment);
super.onDestroyView();
}
@Override
public void onDestroy() {
super.onDestroy();
if (mThumbnailer != null)
mThumbnailer.clearJobs();
mBarrier.reset();
mVideoAdapter.clear();
}
private boolean hasSpaceForGrid(View v) {
final Activity activity = getActivity();
if (activity == null)
return true;
final GridView grid = (GridView)v.findViewById(android.R.id.list);
DisplayMetrics outMetrics = new DisplayMetrics();
activity.getWindowManager().getDefaultDisplay().getMetrics(outMetrics);
final int itemWidth = Util.convertDpToPx(GRID_ITEM_WIDTH_DP);
final int horizontalspacing = Util.convertDpToPx(GRID_HORIZONTAL_SPACING_DP);
final int width = grid.getPaddingLeft() + grid.getPaddingRight() + horizontalspacing + (itemWidth * 2);
if (width < outMetrics.widthPixels)
return true;
return false;
}
private void updateViewMode() {
if (getView() == null || getActivity() == null) {
Log.w(TAG, "Unable to setup the view");
return;
}
GridView gv = (GridView)getView().findViewById(android.R.id.list);
// Compute the left/right padding dynamically
DisplayMetrics outMetrics = new DisplayMetrics();
getActivity().getWindowManager().getDefaultDisplay().getMetrics(outMetrics);
int sidePadding = (int) (outMetrics.widthPixels / 100 * Math.pow(outMetrics.density, 3) / 2);
sidePadding = Math.max(0, Math.min(100, sidePadding));
gv.setPadding(sidePadding, gv.getPaddingTop(), sidePadding, gv.getPaddingBottom());
// Select between grid or list
if (hasSpaceForGrid(getView())) {
Log.d(TAG, "Switching to grid mode");
gv.setNumColumns(GridView.AUTO_FIT);
gv.setStretchMode(GRID_STRETCH_MODE);
gv.setHorizontalSpacing(Util.convertDpToPx(GRID_HORIZONTAL_SPACING_DP));
gv.setVerticalSpacing(Util.convertDpToPx(GRID_VERTICAL_SPACING_DP));
gv.setColumnWidth(Util.convertDpToPx(GRID_ITEM_WIDTH_DP));
mVideoAdapter.setListMode(false);
} else {
Log.d(TAG, "Switching to list mode");
gv.setNumColumns(1);
gv.setStretchMode(LIST_STRETCH_MODE);
gv.setHorizontalSpacing(LIST_HORIZONTAL_SPACING_DP);
gv.setVerticalSpacing(Util.convertDpToPx(LIST_VERTICAL_SPACING_DP));
mVideoAdapter.setListMode(true);
}
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE ||
newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
updateViewMode();
}
}
@Override
public void onGridItemClick(GridView l, View v, int position, long id) {
Media media = (Media) getListAdapter().getItem(position);
if (media instanceof MediaGroup) {
VideoGridFragment videoList = new VideoGridFragment();
videoList.setGroup(media.getTitle());
MainActivity.ShowFragment(getActivity(), "videolist", videoList);
}
else
playVideo(media, false);
super.onGridItemClick(l, v, position, id);
}
protected void playVideo(Media media, boolean fromStart) {
VideoPlayerActivity.start(getActivity(), media.getLocation(), fromStart);
}
protected void playAudio(Media media) {
AudioServiceController.getInstance().load(media.getLocation(), true);
}
private boolean handleContextItemSelected(MenuItem menu, int position) {
Media media = mVideoAdapter.getItem(position);
if (media instanceof MediaGroup)
return true;
switch (menu.getItemId())
{
case R.id.video_list_play:
playVideo(media, false);
return true;
case R.id.video_list_play_from_start:
playVideo(media, true);
return true;
case R.id.video_list_play_audio:
playAudio(media);
return true;
case R.id.video_list_info:
Intent intent = new Intent(getActivity(), MediaInfoActivity.class);
intent.putExtra("itemLocation", media.getLocation());
startActivity(intent);
return true;
case R.id.video_list_delete:
AlertDialog alertDialog = CommonDialogs.deleteMedia(
getActivity(),
media.getLocation(),
new VlcRunnable(media) {
@Override
public void run(Object o) {
Media media = (Media) o;
mMediaLibrary.getMediaItems().remove(media);
mVideoAdapter.remove(media);
}
});
alertDialog.show();
return true;
}
return false;
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
MenuInflater inflater = getActivity().getMenuInflater();
inflater.inflate(R.menu.video_list, menu);
}
@Override
public boolean onContextItemSelected(MenuItem menu) {
AdapterContextMenuInfo info = (AdapterContextMenuInfo) menu.getMenuInfo();
if (info != null && handleContextItemSelected(menu, info.position))
return true;
return super.onContextItemSelected(menu);
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public void onContextPopupMenu(View anchor, final int position) {
if (!Util.isHoneycombOrLater()) {
// Call the "classic" context menu
anchor.performLongClick();
return;
}
PopupMenu popupMenu = new PopupMenu(getActivity(), anchor);
popupMenu.getMenuInflater().inflate(R.menu.video_list, popupMenu.getMenu());
popupMenu.setOnMenuItemClickListener(new OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
return handleContextItemSelected(item, position);
}
});
popupMenu.show();
}
/**
* Handle changes on the list
*/
private Handler mHandler = new VideoListHandler(this);
private static class VideoListHandler extends WeakHandler<VideoGridFragment> {
public VideoListHandler(VideoGridFragment owner) {
super(owner);
}
@Override
public void handleMessage(Message msg) {
VideoGridFragment fragment = getOwner();
if(fragment == null) return;
switch (msg.what) {
case UPDATE_ITEM:
fragment.updateItem();
break;
case MediaLibrary.MEDIA_ITEMS_UPDATED:
// Don't update the adapter while the layout animation is running
if (fragment.mAnimator.isAnimationDone())
fragment.updateList();
else
sendEmptyMessageDelayed(msg.what, 500);
break;
}
}
};
private void updateItem() {
mVideoAdapter.update(mItemToUpdate);
try {
mBarrier.await();
} catch (InterruptedException e) {
} catch (BrokenBarrierException e) {
}
}
private void updateList() {
List<Media> itemList = mMediaLibrary.getVideoItems();
if (mThumbnailer != null)
mThumbnailer.clearJobs();
else
Log.w(TAG, "Can't generate thumbnails, the thumbnailer is missing");
mVideoAdapter.clear();
if (itemList.size() > 0) {
if (mGroup != null || itemList.size() <= 10) {
for (Media item : itemList) {
if (mGroup == null || item.getTitle().startsWith(mGroup)) {
mVideoAdapter.add(item);
if (mThumbnailer != null)
mThumbnailer.addJob(item);
}
}
}
else {
List<MediaGroup> groups = MediaGroup.group(itemList);
for (MediaGroup item : groups) {
mVideoAdapter.add(item.getMedia());
if (mThumbnailer != null)
mThumbnailer.addJob(item);
}
}
mVideoAdapter.sort();
}
}
@Override
public void sortBy(int sortby) {
mVideoAdapter.sortBy(sortby);
}
public void setItemToUpdate(Media item) {
mItemToUpdate = item;
mHandler.sendEmptyMessage(UPDATE_ITEM);
}
public void setGroup(String prefix) {
mGroup = prefix;
}
public void await() throws InterruptedException, BrokenBarrierException {
mBarrier.await();
}
public void resetBarrier() {
mBarrier.reset();
}
private final BroadcastReceiver messageReceiverVideoListFragment = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equalsIgnoreCase(ACTION_SCAN_START)) {
mLayoutFlipperLoading.setVisibility(View.VISIBLE);
mTextViewNomedia.setVisibility(View.INVISIBLE);
} else if (action.equalsIgnoreCase(ACTION_SCAN_STOP)) {
mLayoutFlipperLoading.setVisibility(View.INVISIBLE);
mTextViewNomedia.setVisibility(View.VISIBLE);
}
}
};
public static void actionScanStart(Context context) {
if (context == null)
return;
Intent intent = new Intent();
intent.setAction(ACTION_SCAN_START);
context.getApplicationContext().sendBroadcast(intent);
}
public static void actionScanStop(Context context) {
if (context == null)
return;
Intent intent = new Intent();
intent.setAction(ACTION_SCAN_STOP);
context.getApplicationContext().sendBroadcast(intent);
}
}