/***************************************************************************** * AudioPlayer.java ***************************************************************************** * Copyright © 2011-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.audio; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.media.AudioManager; import android.os.Bundle; import android.os.Handler; import android.preference.PreferenceManager; import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentManager; 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.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.AdapterView.AdapterContextMenuInfo; import android.widget.AdapterView.OnItemClickListener; import android.widget.AdapterView.OnItemLongClickListener; import android.widget.ImageButton; import android.widget.ProgressBar; import android.widget.SeekBar; import android.widget.SeekBar.OnSeekBarChangeListener; import android.widget.TextView; import android.widget.ViewSwitcher; import org.videolan.libvlc.Media; import org.videolan.libvlc.MediaPlayer; import org.videolan.vlc.MediaWrapper; import org.videolan.vlc.PlaybackService; import org.videolan.vlc.R; import org.videolan.vlc.VLCApplication; import org.videolan.vlc.gui.AudioPlayerContainerActivity; import org.videolan.vlc.gui.PlaybackServiceFragment; import org.videolan.vlc.gui.PreferencesActivity; import org.videolan.vlc.gui.audio.widget.CoverMediaSwitcher; import org.videolan.vlc.gui.audio.widget.HeaderMediaSwitcher; import org.videolan.vlc.gui.dialogs.AdvOptionsDialog; import org.videolan.vlc.gui.dialogs.SavePlaylistDialog; import org.videolan.vlc.util.Strings; import org.videolan.vlc.util.Util; import org.videolan.vlc.widget.AudioMediaSwitcher.AudioMediaSwitcherListener; import java.util.ArrayList; import java.util.List; public class AudioPlayer extends PlaybackServiceFragment implements PlaybackService.Callback, View.OnClickListener { public static final String TAG = "VLC/AudioPlayer"; private ProgressBar mProgressBar; private HeaderMediaSwitcher mHeaderMediaSwitcher; private CoverMediaSwitcher mCoverMediaSwitcher; private TextView mTime; private TextView mHeaderTime; private TextView mLength; private ImageButton mResumeToVideo; private ImageButton mPlayPause; private ImageButton mHeaderPlayPause; private ImageButton mNext; private ImageButton mPrevious; private ImageButton mShuffle; private ImageButton mRepeat; private ImageButton mAdvFunc; private ImageButton mPlaylistSwitch, mPlaylistSave; private SeekBar mTimeline; private AudioPlaylistView mSongsList; ViewSwitcher mSwitcher; private boolean mShowRemainingTime = false; private boolean mPreviewingSeek = false; private boolean mSwitchedToVideo = false; private AudioPlaylistAdapter mSongsListAdapter; private boolean mAdvFuncVisible; private boolean mPlaylistSwitchVisible; private boolean mPlaylistSaveVisible; private boolean mHeaderPlayPauseVisible; private boolean mProgressBarVisible; private boolean mHeaderTimeVisible; // Tips private static final String PREF_PLAYLIST_TIPS_SHOWN = "playlist_tips_shown"; private static final String PREF_AUDIOPLAYER_TIPS_SHOWN = "audioplayer_tips_shown"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mSongsListAdapter = new AudioPlaylistAdapter(getActivity()); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View v = inflater.inflate(R.layout.audio_player, container, false); mProgressBar = (ProgressBar) v.findViewById(R.id.progressBar); mHeaderMediaSwitcher = (HeaderMediaSwitcher) v.findViewById(R.id.audio_media_switcher); mHeaderMediaSwitcher.setAudioMediaSwitcherListener(mHeaderMediaSwitcherListener); mCoverMediaSwitcher = (CoverMediaSwitcher) v.findViewById(R.id.cover_media_switcher); mCoverMediaSwitcher.setAudioMediaSwitcherListener(mCoverMediaSwitcherListener); mTime = (TextView) v.findViewById(R.id.time); mHeaderTime = (TextView) v.findViewById(R.id.header_time); mLength = (TextView) v.findViewById(R.id.length); mResumeToVideo = (ImageButton) v.findViewById(R.id.playlist_playasaudio_off); mPlayPause = (ImageButton) v.findViewById(R.id.play_pause); mHeaderPlayPause = (ImageButton) v.findViewById(R.id.header_play_pause); mNext = (ImageButton) v.findViewById(R.id.next); mPrevious = (ImageButton) v.findViewById(R.id.previous); mShuffle = (ImageButton) v.findViewById(R.id.shuffle); mRepeat = (ImageButton) v.findViewById(R.id.repeat); mAdvFunc = (ImageButton) v.findViewById(R.id.adv_function); mPlaylistSwitch = (ImageButton) v.findViewById(R.id.playlist_switch); mPlaylistSave = (ImageButton) v.findViewById(R.id.playlist_save); mTimeline = (SeekBar) v.findViewById(R.id.timeline); mSongsList = (AudioPlaylistView) v.findViewById(R.id.songs_list); mSongsList.setAdapter(mSongsListAdapter); mSwitcher = (ViewSwitcher) v.findViewById(R.id.view_switcher); mSwitcher.setInAnimation(getActivity(), android.R.anim.fade_in); mSwitcher.setOutAnimation(getActivity(), android.R.anim.fade_out); mAdvFuncVisible = false; mPlaylistSwitchVisible = false; mPlaylistSaveVisible = false; mHeaderPlayPauseVisible = true; mProgressBarVisible = true; mHeaderTimeVisible = true; restoreHedaderButtonVisibilities(); mTime.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { onTimeLabelClick(v); } }); mResumeToVideo.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (mService != null) { mService.switchToVideo(); mSwitchedToVideo = true; } } }); mPlayPause.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { onPlayPauseClick(v); } }); mPlayPause.setOnLongClickListener(new View.OnLongClickListener() { @Override public boolean onLongClick(View v) { onStopClick(v); return true; } }); mHeaderPlayPause.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { onPlayPauseClick(v); } }); mHeaderPlayPause.setOnLongClickListener(new View.OnLongClickListener() { @Override public boolean onLongClick(View v) { onStopClick(v); return true; } }); mNext.setOnTouchListener(new LongSeekListener(true, Util.getResourceFromAttribute(getActivity(), R.attr.ic_next), R.drawable.ic_next_pressed)); mPrevious.setOnTouchListener(new LongSeekListener(false, Util.getResourceFromAttribute(getActivity(), R.attr.ic_previous), R.drawable.ic_previous_pressed)); mShuffle.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { onShuffleClick(v); } }); mRepeat.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { onRepeatClick(v); } }); mAdvFunc.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { showAdvancedOptions(v); } }); mPlaylistSave.setOnClickListener(this); mPlaylistSwitch.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mSwitcher.showNext(); if (mSwitcher.getDisplayedChild() == 0) mPlaylistSwitch.setImageResource(Util.getResourceFromAttribute(getActivity(), R.attr.ic_playlist_on)); else mPlaylistSwitch.setImageResource(Util.getResourceFromAttribute(getActivity(), R.attr.ic_playlist)); } }); mSongsList.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> av, View v, int p, long id) { if (mService != null) { mService.playIndex(p); } } }); mSongsList.setOnItemLongClickListener(new OnItemLongClickListener() { @Override public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { getActivity().openContextMenu(view); return true; } }); mSongsList.setOnItemDraggedListener(new AudioPlaylistView.OnItemDraggedListener() { @Override public void onItemDragged(int positionStart, int positionEnd) { if (mService != null) mService.moveItem(positionStart, positionEnd); } }); mSongsList.setOnItemRemovedListener(new AudioPlaylistView.OnItemRemovedListener() { @Override public void onItemRemoved(int position) { if (mService != null) mService.remove(position); update(); } }); registerForContextMenu(mSongsList); getActivity().setVolumeControlStream(AudioManager.STREAM_MUSIC); return v; } @Override public void onResume() { super.onResume(); } @Override public void onPause() { super.onPause(); getView().cancelLongPress(); } @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { MenuInflater menuInflater = getActivity().getMenuInflater(); menuInflater.inflate(R.menu.audio_player, menu); } @Override public boolean onContextItemSelected(MenuItem item) { if (getUserVisibleHint() && item.getMenuInfo() instanceof AdapterContextMenuInfo) { AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo(); if(info == null) // info can be null return super.onContextItemSelected(item); int id = item.getItemId(); if(id == R.id.audio_player_mini_remove) { Log.d(TAG, "Context menu removing " + info.position); if (mService != null) mService.remove(info.position); return true; } return super.onContextItemSelected(item); } else return false; } /** * Show the audio player from an intent * * @param context The context of the activity */ public static void start(Context context) { Intent intent = new Intent(); intent.setAction(AudioPlayerContainerActivity.ACTION_SHOW_PLAYER); context.getApplicationContext().sendBroadcast(intent); } @Override public void update() { if (mService == null || getActivity() == null) return; if (mService.hasMedia() && !mService.isVideoPlaying()) { SharedPreferences mSettings= PreferenceManager.getDefaultSharedPreferences(getActivity()); if (mSettings.getBoolean(PreferencesActivity.VIDEO_RESTORE, false)){ Util.commitPreferences(mSettings.edit().putBoolean(PreferencesActivity.VIDEO_RESTORE, false)); mService.switchToVideo(); mSwitchedToVideo = true; return; } else show(); } else { hide(); return; } mHeaderMediaSwitcher.updateMedia(mService); mCoverMediaSwitcher.updateMedia(mService); FragmentActivity act = getActivity(); mResumeToVideo.setVisibility(mService.getVideoTracksCount() > 0 ? View.VISIBLE : View.GONE); if (mService.isPlaying()) { mPlayPause.setImageResource(Util.getResourceFromAttribute(act, R.attr.ic_pause)); mPlayPause.setContentDescription(getString(R.string.pause)); mHeaderPlayPause.setImageResource(Util.getResourceFromAttribute(act, R.attr.ic_pause)); mHeaderPlayPause.setContentDescription(getString(R.string.pause)); } else { mPlayPause.setImageResource(Util.getResourceFromAttribute(act, R.attr.ic_play)); mPlayPause.setContentDescription(getString(R.string.play)); mHeaderPlayPause.setImageResource(Util.getResourceFromAttribute(act, R.attr.ic_play)); mHeaderPlayPause.setContentDescription(getString(R.string.play)); } if (mService.isShuffling()) { mShuffle.setImageResource(Util.getResourceFromAttribute(act, R.attr.ic_shuffle_on)); mShuffle.setContentDescription(getResources().getString(R.string.shuffle_on)); } else { mShuffle.setImageResource(Util.getResourceFromAttribute(act, R.attr.ic_shuffle)); mShuffle.setContentDescription(getResources().getString(R.string.shuffle)); } switch(mService.getRepeatType()) { case None: mRepeat.setImageResource(Util.getResourceFromAttribute(act, R.attr.ic_repeat)); mRepeat.setContentDescription(getResources().getString(R.string.repeat)); break; case Once: mRepeat.setImageResource(Util.getResourceFromAttribute(act, R.attr.ic_repeat_one)); mRepeat.setContentDescription(getResources().getString(R.string.repeat_single)); break; default: case All: mRepeat.setImageResource(Util.getResourceFromAttribute(act, R.attr.ic_repeat_on)); mRepeat.setContentDescription(getResources().getString(R.string.repeat_all)); break; } final List<String> mediaLocations = mService.getMediaLocations(); mShuffle.setVisibility(mediaLocations != null && mediaLocations.size() > 2 ? View.VISIBLE : View.INVISIBLE); if (mService.hasNext()) mNext.setVisibility(ImageButton.VISIBLE); else mNext.setVisibility(ImageButton.INVISIBLE); if (mService.hasPrevious()) mPrevious.setVisibility(ImageButton.VISIBLE); else mPrevious.setVisibility(ImageButton.INVISIBLE); mTimeline.setOnSeekBarChangeListener(mTimelineListner); updateList(); } @Override public void updateProgress() { if (mService == null) return; int time = (int) mService.getTime(); int length = (int) mService.getLength(); mHeaderTime.setText(Strings.millisToString(time)); mLength.setText(Strings.millisToString(length)); mTimeline.setMax(length); mProgressBar.setMax(length); if(!mPreviewingSeek) { mTime.setText(Strings.millisToString(mShowRemainingTime ? time-length : time)); mTimeline.setProgress(time); mProgressBar.setProgress(time); } } @Override public void onMediaEvent(Media.Event event) { } @Override public void onMediaPlayerEvent(MediaPlayer.Event event) { switch (event.type) { case MediaPlayer.Event.Opening: mSwitchedToVideo = false; break; case MediaPlayer.Event.ESAdded: final boolean forceAudio = (mService.getCurrentMediaWrapper().getFlags() & MediaWrapper.MEDIA_FORCE_AUDIO) != 0; if (!forceAudio && !mSwitchedToVideo && event.getEsChangedType() == Media.Track.Type.Video) { mService.switchToVideo(); mSwitchedToVideo = true; } break; } } private void updateList() { int currentIndex = -1; if (mService == null) return; mSongsListAdapter.clear(); final List<MediaWrapper> audioList = mService.getMedias(); final String currentItem = mService.getCurrentMediaLocation(); if (audioList != null) { for (int i = 0; i < audioList.size(); i++) { final MediaWrapper media = audioList.get(i); if (currentItem != null && currentItem.equals(media.getLocation())) currentIndex = i; mSongsListAdapter.add(media); } } mSongsListAdapter.setCurrentIndex(currentIndex); mSongsList.setSelection(currentIndex); mSongsListAdapter.notifyDataSetChanged(); } OnSeekBarChangeListener mTimelineListner = new OnSeekBarChangeListener() { @Override public void onStopTrackingTouch(SeekBar arg0) { // TODO Auto-generated method stub } @Override public void onStartTrackingTouch(SeekBar arg0) { // TODO Auto-generated method stub } @Override public void onProgressChanged(SeekBar sb, int prog, boolean fromUser) { if (fromUser && mService != null) { mService.setTime(prog); mTime.setText(Strings.millisToString(mShowRemainingTime ? prog- mService.getLength() : prog)); mHeaderTime.setText(Strings.millisToString(prog)); } } }; public void onTimeLabelClick(View view) { mShowRemainingTime = !mShowRemainingTime; update(); } public void onPlayPauseClick(View view) { if (mService == null) return; if (mService.isPlaying()) { mService.pause(); } else { mService.play(); } } public void onStopClick(View view) { if (mService != null) mService.stop(); } public void onNextClick(View view) { if (mService != null) mService.next(); } public void onPreviousClick(View view) { if (mService != null) mService.previous(); } public void onRepeatClick(View view) { if (mService == null) return; switch (mService.getRepeatType()) { case None: mService.setRepeatType(PlaybackService.RepeatType.All); break; case All: mService.setRepeatType(PlaybackService.RepeatType.Once); break; default: case Once: mService.setRepeatType(PlaybackService.RepeatType.None); break; } update(); } public void onShuffleClick(View view) { if (mService != null) mService.shuffle(); update(); } public void showAdvancedOptions(View v) { FragmentManager fm = getActivity().getSupportFragmentManager(); AdvOptionsDialog advOptionsDialog = new AdvOptionsDialog(); Bundle args = new Bundle(); args.putInt(AdvOptionsDialog.MODE_KEY, AdvOptionsDialog.MODE_AUDIO); advOptionsDialog.setArguments(args); advOptionsDialog.show(fm, "fragment_adv_options"); } public void show() { AudioPlayerContainerActivity activity = (AudioPlayerContainerActivity)getActivity(); if (activity != null) activity.showAudioPlayer(); } public void hide() { AudioPlayerContainerActivity activity = (AudioPlayerContainerActivity)getActivity(); if (activity != null) activity.hideAudioPlayer(); } /** * Set the visibilities of the player header elements. * @param advFuncVisible * @param playlistSwitchVisible * @param headerPlayPauseVisible */ public void setHeaderVisibilities(boolean advFuncVisible, boolean playlistSwitchVisible, boolean headerPlayPauseVisible, boolean progressBarVisible, boolean headerTimeVisible, boolean playlistSaveVisible) { mAdvFuncVisible = advFuncVisible; mPlaylistSwitchVisible = playlistSwitchVisible; mHeaderPlayPauseVisible = headerPlayPauseVisible; mProgressBarVisible = progressBarVisible; mHeaderTimeVisible = headerTimeVisible; mPlaylistSaveVisible = playlistSaveVisible; restoreHedaderButtonVisibilities(); } private void restoreHedaderButtonVisibilities() { mAdvFunc.setVisibility(mAdvFuncVisible ? ImageButton.VISIBLE : ImageButton.GONE); mPlaylistSwitch.setVisibility(mPlaylistSwitchVisible ? ImageButton.VISIBLE : ImageButton.GONE); mPlaylistSave.setVisibility(mPlaylistSaveVisible ? ImageButton.VISIBLE : ImageButton.GONE); mHeaderPlayPause.setVisibility(mHeaderPlayPauseVisible ? ImageButton.VISIBLE : ImageButton.GONE); mProgressBar.setVisibility(mProgressBarVisible ? ProgressBar.VISIBLE : ProgressBar.GONE); mHeaderTime.setVisibility(mHeaderTimeVisible ? TextView.VISIBLE : TextView.GONE); } private void hideHedaderButtons() { mAdvFunc.setVisibility(ImageButton.GONE); mPlaylistSwitch.setVisibility(ImageButton.GONE); mPlaylistSave.setVisibility(ImageButton.GONE); mHeaderPlayPause.setVisibility(ImageButton.GONE); mHeaderTime.setVisibility(TextView.GONE); } private final AudioMediaSwitcherListener mHeaderMediaSwitcherListener = new AudioMediaSwitcherListener() { @Override public void onMediaSwitching() {} @Override public void onMediaSwitched(int position) { if (mService == null) return; if (position == AudioMediaSwitcherListener.PREVIOUS_MEDIA) mService.previous(); else if (position == AudioMediaSwitcherListener.NEXT_MEDIA) mService.next(); } @Override public void onTouchDown() { hideHedaderButtons(); } @Override public void onTouchUp() { restoreHedaderButtonVisibilities(); } @Override public void onTouchClick() { AudioPlayerContainerActivity activity = (AudioPlayerContainerActivity)getActivity(); activity.slideUpOrDownAudioPlayer(); } }; private final AudioMediaSwitcherListener mCoverMediaSwitcherListener = new AudioMediaSwitcherListener() { @Override public void onMediaSwitching() {} @Override public void onMediaSwitched(int position) { if (mService == null) return; if (position == AudioMediaSwitcherListener.PREVIOUS_MEDIA) mService.previous(); else if (position == AudioMediaSwitcherListener.NEXT_MEDIA) mService.next(); } @Override public void onTouchDown() {} @Override public void onTouchUp() {} @Override public void onTouchClick() {} }; @Override public void onClick(View v) { switch (v.getId()){ case R.id.playlist_save: if (mService == null) return; FragmentManager fm = getActivity().getSupportFragmentManager(); SavePlaylistDialog savePlaylistDialog = new SavePlaylistDialog(); Bundle args = new Bundle(); args.putParcelableArrayList(SavePlaylistDialog.KEY_TRACKS, (ArrayList<MediaWrapper>) mService.getMedias()); savePlaylistDialog.setArguments(args); savePlaylistDialog.show(fm, "fragment_save_playlist"); break; } } @Override public void onConnected(PlaybackService service) { super.onConnected(service); mService.addCallback(this); update(); } @Override public void onStop() { /* unregister before super.onStop() since mService is set to null from this call */ if (mService != null) mService.removeCallback(this); super.onStop(); } class LongSeekListener implements View.OnTouchListener { boolean forward; int normal, pressed; long length; public LongSeekListener(boolean forwards, int normalRes, int pressedRes) { this.forward = forwards; this.normal = normalRes; this.pressed = pressedRes; this.length = -1; } int possibleSeek; boolean vibrated; Runnable seekRunnable = new Runnable() { @Override public void run() { if(!vibrated) { ((android.os.Vibrator) VLCApplication.getAppContext().getSystemService(Context.VIBRATOR_SERVICE)) .vibrate(80); vibrated = true; } if(forward) { if(length <= 0 || possibleSeek < length) possibleSeek += 4000; } else { if(possibleSeek > 4000) possibleSeek -= 4000; else if(possibleSeek <= 4000) possibleSeek = 0; } mTime.setText(Strings.millisToString(mShowRemainingTime ? possibleSeek-length : possibleSeek)); mTimeline.setProgress(possibleSeek); mProgressBar.setProgress(possibleSeek); h.postDelayed(seekRunnable, 50); } }; Handler h = new Handler(); @Override public boolean onTouch(View v, MotionEvent event) { if (mService == null) return false; switch(event.getAction()) { case MotionEvent.ACTION_DOWN: (forward ? mNext : mPrevious).setImageResource(this.pressed); possibleSeek = (int) mService.getTime(); mPreviewingSeek = true; vibrated = false; length = mService.getLength(); h.postDelayed(seekRunnable, 1000); return true; case MotionEvent.ACTION_UP: (forward ? mNext : mPrevious).setImageResource(this.normal); h.removeCallbacks(seekRunnable); mPreviewingSeek = false; if(event.getEventTime()-event.getDownTime() < 1000) { if(forward) onNextClick(v); else onPreviousClick(v); } else { if(forward) { if(possibleSeek < mService.getLength()) mService.setTime(possibleSeek); else onNextClick(v); } else { if(possibleSeek > 0) mService.setTime(possibleSeek); else onPreviousClick(v); } } return true; } return false; } } public void showPlaylistTips() { AudioPlayerContainerActivity activity = (AudioPlayerContainerActivity)getActivity(); if(activity != null) activity.showTipViewIfNeeded(R.layout.audio_playlist_tips, PREF_PLAYLIST_TIPS_SHOWN); } public void showAudioPlayerTips() { AudioPlayerContainerActivity activity = (AudioPlayerContainerActivity)getActivity(); if(activity != null) activity.showTipViewIfNeeded(R.layout.audio_player_tips, PREF_AUDIOPLAYER_TIPS_SHOWN); } /* * Override this method to prefent NPE on mFragmentManager reference. */ @Override public void setUserVisibleHint(boolean isVisibleToUser) { if (getFragmentManager() != null) super.setUserVisibleHint(isVisibleToUser); } }