Java tutorial
/***************************************************************************** * AudioService.java ***************************************************************************** * Copyright 2011-2013 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; import android.annotation.TargetApi; import android.app.Notification; import android.app.PendingIntent; import android.app.Service; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.media.AudioManager; import android.media.AudioManager.OnAudioFocusChangeListener; import android.media.MediaMetadataRetriever; import android.media.RemoteControlClient; import android.media.RemoteControlClient.MetadataEditor; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.PowerManager; import android.os.RemoteException; import android.preference.PreferenceManager; import android.support.annotation.Nullable; import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationManagerCompat; import android.telephony.TelephonyManager; import android.util.Log; import android.view.View; import android.widget.RemoteViews; import android.widget.Toast; import org.videolan.libvlc.EventHandler; import org.videolan.libvlc.LibVLC; import org.videolan.libvlc.Media; import org.videolan.libvlc.MediaPlayer; import org.videolan.libvlc.util.AndroidUtil; import org.videolan.vlc.gui.MainActivity; import org.videolan.vlc.gui.AudioPlayerContainerActivity; import org.videolan.vlc.gui.audio.AudioUtil; import org.videolan.vlc.gui.video.VideoPlayerActivity; import org.videolan.vlc.interfaces.IPlaybackService; import org.videolan.vlc.interfaces.IPlaybackServiceCallback; import org.videolan.vlc.util.Util; import org.videolan.vlc.util.VLCInstance; import org.videolan.vlc.util.VLCOptions; import org.videolan.vlc.util.WeakHandler; import org.videolan.vlc.widget.VLCAppWidgetProvider; import java.io.File; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Calendar; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Random; import java.util.Stack; import java.util.concurrent.atomic.AtomicBoolean; public class PlaybackService extends Service { private static final String TAG = "VLC/AudioService"; private static final int SHOW_PROGRESS = 0; private static final int SHOW_TOAST = 1; public static final String START_FROM_NOTIFICATION = "from_notification"; public static final String ACTION_REMOTE_GENERIC = "org.videolan.vlc.remote."; public static final String ACTION_REMOTE_BACKWARD = "org.videolan.vlc.remote.Backward"; public static final String ACTION_REMOTE_PLAY = "org.videolan.vlc.remote.Play"; public static final String ACTION_REMOTE_PLAYPAUSE = "org.videolan.vlc.remote.PlayPause"; public static final String ACTION_REMOTE_PAUSE = "org.videolan.vlc.remote.Pause"; public static final String ACTION_REMOTE_STOP = "org.videolan.vlc.remote.Stop"; public static final String ACTION_REMOTE_FORWARD = "org.videolan.vlc.remote.Forward"; public static final String ACTION_REMOTE_LAST_PLAYLIST = "org.videolan.vlc.remote.LastPlaylist"; public static final String ACTION_WIDGET_INIT = "org.videolan.vlc.widget.INIT"; public static final String ACTION_WIDGET_UPDATE = "org.videolan.vlc.widget.UPDATE"; public static final String ACTION_WIDGET_UPDATE_COVER = "org.videolan.vlc.widget.UPDATE_COVER"; public static final String ACTION_WIDGET_UPDATE_POSITION = "org.videolan.vlc.widget.UPDATE_POSITION"; private MediaWrapperListPlayer mMediaListPlayer; private boolean mForceAudio = false; private HashMap<IPlaybackServiceCallback, Integer> mCallback; private EventHandler mEventHandler; private OnAudioFocusChangeListener audioFocusListener; private boolean mDetectHeadset = true; private boolean mPebbleEnabled; private PowerManager.WakeLock mWakeLock; private final AtomicBoolean mExpanding = new AtomicBoolean(false); private static boolean mWasPlayingAudio = false; // Index management /** * Stack of previously played indexes, used in shuffle mode */ private Stack<Integer> mPrevious; private int mCurrentIndex; // Set to -1 if no media is currently loaded private int mPrevIndex; // Set to -1 if no previous media private int mNextIndex; // Set to -1 if no next media // Playback management private boolean mShuffling = false; private RepeatType mRepeating = RepeatType.None; private Random mRandom = null; // Used in shuffling process // RemoteControlClient-related /** * RemoteControlClient is for lock screen playback control. */ private RemoteControlClient mRemoteControlClient = null; private RemoteControlClientReceiver mRemoteControlClientReceiver = null; /** * Last widget position update timestamp */ private long mWidgetPositionTimestamp = Calendar.getInstance().getTimeInMillis(); private ComponentName mRemoteControlClientReceiverComponent; private static LibVLC LibVLC() { return VLCInstance.get(); } private static MediaPlayer MediaPlayer() { return VLCInstance.getMainMediaPlayer(); } public static enum RepeatType { None, Once, All } @Override public void onCreate() { super.onCreate(); if (!VLCInstance.testCompatibleCPU(this)) { stopSelf(); return; } SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); mDetectHeadset = prefs.getBoolean("enable_headset_detection", true); mMediaListPlayer = MediaWrapperListPlayer.getInstance(); mCallback = new HashMap<IPlaybackServiceCallback, Integer>(); mCurrentIndex = -1; mPrevIndex = -1; mNextIndex = -1; mPrevious = new Stack<Integer>(); mEventHandler = EventHandler.getInstance(); mRemoteControlClientReceiverComponent = new ComponentName(BuildConfig.APPLICATION_ID, RemoteControlClientReceiver.class.getName()); // Make sure the audio player will acquire a wake-lock while playing. If we don't do // that, the CPU might go to sleep while the song is playing, causing playback to stop. PowerManager pm = (PowerManager) VLCApplication.getAppContext().getSystemService(Context.POWER_SERVICE); mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); IntentFilter filter = new IntentFilter(); filter.setPriority(Integer.MAX_VALUE); filter.addAction(ACTION_REMOTE_BACKWARD); filter.addAction(ACTION_REMOTE_PLAYPAUSE); filter.addAction(ACTION_REMOTE_PLAY); filter.addAction(ACTION_REMOTE_PAUSE); filter.addAction(ACTION_REMOTE_STOP); filter.addAction(ACTION_REMOTE_FORWARD); filter.addAction(ACTION_REMOTE_LAST_PLAYLIST); filter.addAction(ACTION_WIDGET_INIT); filter.addAction(Intent.ACTION_HEADSET_PLUG); filter.addAction(AudioManager.ACTION_AUDIO_BECOMING_NOISY); filter.addAction(VLCApplication.SLEEP_INTENT); filter.addAction(VLCApplication.INCOMING_CALL_INTENT); filter.addAction(VLCApplication.CALL_ENDED_INTENT); registerReceiver(serviceReceiver, filter); final SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(this); boolean stealRemoteControl = pref.getBoolean("enable_steal_remote_control", false); if (!AndroidUtil.isFroyoOrLater() || stealRemoteControl) { /* Backward compatibility for API 7 */ filter = new IntentFilter(); if (stealRemoteControl) filter.setPriority(Integer.MAX_VALUE); filter.addAction(Intent.ACTION_MEDIA_BUTTON); mRemoteControlClientReceiver = new RemoteControlClientReceiver(); registerReceiver(mRemoteControlClientReceiver, filter); } try { getPackageManager().getPackageInfo("com.getpebble.android", PackageManager.GET_ACTIVITIES); mPebbleEnabled = true; } catch (PackageManager.NameNotFoundException e) { mPebbleEnabled = false; } } /** * Set up the remote control and tell the system we want to be the default receiver for the MEDIA buttons * @see http://android-developers.blogspot.fr/2010/06/allowing-applications-to-play-nicer.html */ @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) public void setUpRemoteControlClient() { Context context = VLCApplication.getAppContext(); AudioManager audioManager = (AudioManager) VLCApplication.getAppContext().getSystemService(AUDIO_SERVICE); if (AndroidUtil.isICSOrLater()) { audioManager.registerMediaButtonEventReceiver(mRemoteControlClientReceiverComponent); if (mRemoteControlClient == null) { Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); mediaButtonIntent.setComponent(mRemoteControlClientReceiverComponent); PendingIntent mediaPendingIntent = PendingIntent.getBroadcast(context, 0, mediaButtonIntent, 0); // create and register the remote control client mRemoteControlClient = new RemoteControlClient(mediaPendingIntent); audioManager.registerRemoteControlClient(mRemoteControlClient); } mRemoteControlClient.setTransportControlFlags(RemoteControlClient.FLAG_KEY_MEDIA_PLAY | RemoteControlClient.FLAG_KEY_MEDIA_PAUSE | RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS | RemoteControlClient.FLAG_KEY_MEDIA_NEXT | RemoteControlClient.FLAG_KEY_MEDIA_STOP); } else if (AndroidUtil.isFroyoOrLater()) { audioManager.registerMediaButtonEventReceiver(mRemoteControlClientReceiverComponent); } } /** * A function to control the Remote Control Client. It is needed for * compatibility with devices below Ice Cream Sandwich (4.0). * * @param state Playback state */ @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) private void setRemoteControlClientPlaybackState(int state) { if (!AndroidUtil.isICSOrLater() || mRemoteControlClient == null) return; switch (state) { case EventHandler.MediaPlayerPlaying: mRemoteControlClient.setPlaybackState(RemoteControlClient.PLAYSTATE_PLAYING); break; case EventHandler.MediaPlayerPaused: mRemoteControlClient.setPlaybackState(RemoteControlClient.PLAYSTATE_PAUSED); break; case EventHandler.MediaPlayerStopped: mRemoteControlClient.setPlaybackState(RemoteControlClient.PLAYSTATE_STOPPED); break; } } @Override public int onStartCommand(Intent intent, int flags, int startId) { if (intent == null) return START_STICKY; if (ACTION_REMOTE_PLAYPAUSE.equals(intent.getAction())) { if (hasCurrentMedia()) return START_STICKY; else loadLastPlaylist(); } else if (ACTION_REMOTE_PLAY.equals(intent.getAction())) { if (hasCurrentMedia()) play(); else loadLastPlaylist(); } updateWidget(); return super.onStartCommand(intent, flags, startId); } @Override public void onDestroy() { super.onDestroy(); stop(); if (mWakeLock.isHeld()) mWakeLock.release(); unregisterReceiver(serviceReceiver); if (mRemoteControlClientReceiver != null) { unregisterReceiver(mRemoteControlClientReceiver); mRemoteControlClientReceiver = null; } } @Override public IBinder onBind(Intent intent) { return mInterface; } @TargetApi(Build.VERSION_CODES.FROYO) private void changeAudioFocus(boolean gain) { if (!AndroidUtil.isFroyoOrLater()) // NOP if not supported return; if (audioFocusListener == null) { audioFocusListener = new OnAudioFocusChangeListener() { @Override public void onAudioFocusChange(int focusChange) { if (!hasCurrentMedia()) return; switch (focusChange) { case AudioManager.AUDIOFOCUS_LOSS: if (MediaPlayer().isPlaying()) MediaPlayer().pause(); break; case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: /* * Lower the volume to 36% to "duck" when an alert or something * needs to be played. */ MediaPlayer().setVolume(36); break; case AudioManager.AUDIOFOCUS_GAIN: case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT: case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK: MediaPlayer().setVolume(100); break; } } }; } AudioManager am = (AudioManager) VLCApplication.getAppContext().getSystemService(AUDIO_SERVICE); if (gain) am.requestAudioFocus(audioFocusListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); else am.abandonAudioFocus(audioFocusListener); } private final BroadcastReceiver serviceReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); int state = intent.getIntExtra("state", 0); if (MediaPlayer() == null) { Log.w(TAG, "Intent received, but VLC is not loaded, skipping."); return; } /* * Incoming Call : Pause if VLC is playing audio or video. */ if (action.equalsIgnoreCase(VLCApplication.INCOMING_CALL_INTENT)) { mWasPlayingAudio = MediaPlayer().isPlaying() && hasCurrentMedia(); if (mWasPlayingAudio) pause(); } /* * Call ended : Play only if VLC was playing audio. */ if (action.equalsIgnoreCase(VLCApplication.CALL_ENDED_INTENT) && mWasPlayingAudio) { play(); } // skip all headsets events if there is a call TelephonyManager telManager = (TelephonyManager) VLCApplication.getAppContext() .getSystemService(Context.TELEPHONY_SERVICE); if (telManager != null && telManager.getCallState() != TelephonyManager.CALL_STATE_IDLE) return; /* * Launch the activity if needed */ if (action.startsWith(ACTION_REMOTE_GENERIC) && !MediaPlayer().isPlaying() && !hasCurrentMedia()) { Intent iVlc = new Intent(context, MainActivity.class); iVlc.putExtra(START_FROM_NOTIFICATION, true); iVlc.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP); context.startActivity(iVlc); } /* * Remote / headset control events */ if (action.equalsIgnoreCase(ACTION_REMOTE_PLAYPAUSE)) { if (MediaPlayer().isPlaying() && hasCurrentMedia()) pause(); else if (!MediaPlayer().isPlaying() && hasCurrentMedia()) play(); } else if (action.equalsIgnoreCase(ACTION_REMOTE_PLAY)) { if (!MediaPlayer().isPlaying() && hasCurrentMedia()) play(); } else if (action.equalsIgnoreCase(ACTION_REMOTE_PAUSE)) { if (MediaPlayer().isPlaying() && hasCurrentMedia()) pause(); } else if (action.equalsIgnoreCase(ACTION_REMOTE_BACKWARD)) { previous(); } else if (action.equalsIgnoreCase(ACTION_REMOTE_STOP)) { stop(); } else if (action.equalsIgnoreCase(ACTION_REMOTE_FORWARD)) { next(); } else if (action.equalsIgnoreCase(ACTION_REMOTE_LAST_PLAYLIST)) { loadLastPlaylist(); } else if (action.equalsIgnoreCase(ACTION_WIDGET_INIT)) { updateWidget(); } /* * headset plug events */ if (mDetectHeadset) { if (action.equalsIgnoreCase(AudioManager.ACTION_AUDIO_BECOMING_NOISY)) { Log.i(TAG, "Headset Removed."); if (MediaPlayer().isPlaying() && hasCurrentMedia()) pause(); } else if (action.equalsIgnoreCase(Intent.ACTION_HEADSET_PLUG) && state != 0) { Log.i(TAG, "Headset Inserted."); if (!MediaPlayer().isPlaying() && hasCurrentMedia()) play(); } } /* * Sleep */ if (action.equalsIgnoreCase(VLCApplication.SLEEP_INTENT)) { stop(); } } }; /** * Handle libvlc asynchronous events */ private final Handler mVlcEventHandler = new AudioServiceEventHandler(this); private static class AudioServiceEventHandler extends WeakHandler<PlaybackService> { public AudioServiceEventHandler(PlaybackService fragment) { super(fragment); } @Override public void handleMessage(Message msg) { PlaybackService service = getOwner(); if (service == null) return; switch (msg.getData().getInt("event")) { case EventHandler.MediaParsedChanged: Log.i(TAG, "MediaParsedChanged"); break; case EventHandler.MediaPlayerPlaying: Log.i(TAG, "MediaPlayerPlaying"); service.executeUpdate(); service.executeUpdateProgress(); final MediaWrapper mw = service.mMediaListPlayer.getMediaList().getMedia(service.mCurrentIndex); if (mw != null) { long length = service.MediaPlayer().getLength(); MediaDatabase dbManager = MediaDatabase.getInstance(); MediaWrapper m = dbManager.getMedia(mw.getUri()); /** * 1) There is a media to update * 2) It has a length of 0 * (dynamic track loading - most notably the OGG container) * 3) We were able to get a length even after parsing * (don't want to replace a 0 with a 0) */ if (m != null && m.getLength() == 0 && length > 0) { dbManager.updateMedia(mw.getUri(), MediaDatabase.mediaColumn.MEDIA_LENGTH, length); } } service.changeAudioFocus(true); service.setRemoteControlClientPlaybackState(EventHandler.MediaPlayerPlaying); service.showNotification(); if (!service.mWakeLock.isHeld()) service.mWakeLock.acquire(); break; case EventHandler.MediaPlayerPaused: Log.i(TAG, "MediaPlayerPaused"); service.executeUpdate(); service.executeUpdateProgress(); service.showNotification(); service.setRemoteControlClientPlaybackState(EventHandler.MediaPlayerPaused); if (service.mWakeLock.isHeld()) service.mWakeLock.release(); break; case EventHandler.MediaPlayerStopped: Log.i(TAG, "MediaPlayerStopped"); service.executeUpdate(); service.executeUpdateProgress(); service.setRemoteControlClientPlaybackState(EventHandler.MediaPlayerStopped); if (service.mWakeLock.isHeld()) service.mWakeLock.release(); break; case EventHandler.MediaPlayerEndReached: Log.i(TAG, "MediaPlayerEndReached"); service.executeUpdate(); service.executeUpdateProgress(); service.determinePrevAndNextIndices(true); service.next(); if (service.mWakeLock.isHeld()) service.mWakeLock.release(); break; case EventHandler.MediaPlayerPositionChanged: float pos = msg.getData().getFloat("data"); service.updateWidgetPosition(pos); break; case EventHandler.MediaPlayerEncounteredError: service.showToast( service.getString(R.string.invalid_location, service.mMediaListPlayer.getMediaList().getMRL(service.mCurrentIndex)), Toast.LENGTH_SHORT); service.executeUpdate(); service.executeUpdateProgress(); service.next(); if (service.mWakeLock.isHeld()) service.mWakeLock.release(); break; case EventHandler.MediaPlayerTimeChanged: // avoid useless error logs break; case EventHandler.MediaMetaChanged: if (!service.hasCurrentMedia()) break; service.getCurrentMedia().updateMeta(service.MediaPlayer()); service.setUpRemoteControlClient(); service.executeUpdate(); service.showNotification(); service.updateRemoteControlClientMetadata(); break; case EventHandler.MediaPlayerESAdded: service.handleVout(); break; default: Log.e(TAG, String.format("Event not handled (0x%x)", msg.getData().getInt("event"))); break; } } }; private final MediaWrapperList.EventListener mListEventListener = new MediaWrapperList.EventListener() { @Override public void onItemAdded(int index, String mrl) { Log.i(TAG, "CustomMediaListItemAdded"); if (mCurrentIndex >= index && !mExpanding.get()) mCurrentIndex++; determinePrevAndNextIndices(); executeUpdate(); } @Override public void onItemRemoved(int index, String mrl) { Log.i(TAG, "CustomMediaListItemDeleted"); if (mCurrentIndex == index && !mExpanding.get()) { // The current item has been deleted mCurrentIndex--; determinePrevAndNextIndices(); if (mNextIndex != -1) next(); else if (mCurrentIndex != -1) { mMediaListPlayer.playIndex(PlaybackService.this, mCurrentIndex, VLCOptions.MEDIA_NO_VIDEO); executeOnMediaPlayedAdded(); } else stop(); } if (mCurrentIndex > index && !mExpanding.get()) mCurrentIndex--; determinePrevAndNextIndices(); executeUpdate(); } @Override public void onItemMoved(int indexBefore, int indexAfter, String mrl) { Log.i(TAG, "CustomMediaListItemMoved"); if (mCurrentIndex == indexBefore) { mCurrentIndex = indexAfter; if (indexAfter > indexBefore) mCurrentIndex--; } else if (indexBefore > mCurrentIndex && indexAfter <= mCurrentIndex) mCurrentIndex++; else if (indexBefore < mCurrentIndex && indexAfter > mCurrentIndex) mCurrentIndex--; // If we are in random mode, we completely reset the stored previous track // as their indices changed. mPrevious.clear(); determinePrevAndNextIndices(); executeUpdate(); } }; private void handleVout() { if (mForceAudio || MediaPlayer().getVideoTracksCount() <= 0 || !hasCurrentMedia()) return; final MediaWrapper mw = getCurrentMedia(); if (mw == null) return; Log.i(TAG, "Obtained video track"); int index = mCurrentIndex; mCurrentIndex = -1; mEventHandler.removeHandler(mVlcEventHandler); // Preserve playback when switching to video hideNotification(false); // Switch to the video player & don't lose the currently playing stream VideoPlayerActivity.startOpened(VLCApplication.getAppContext(), index); } private void executeUpdate() { executeUpdate(true); } private void executeUpdate(Boolean updateWidget) { for (IPlaybackServiceCallback callback : mCallback.keySet()) { try { callback.update(); } catch (RemoteException e) { e.printStackTrace(); } } if (updateWidget) updateWidget(); } private void executeUpdateProgress() { for (IPlaybackServiceCallback callback : mCallback.keySet()) { try { callback.updateProgress(); } catch (RemoteException e) { e.printStackTrace(); } } } private void executeOnMediaPlayedAdded() { final MediaWrapper media = getCurrentMedia(); for (IPlaybackServiceCallback callback : mCallback.keySet()) { try { callback.onMediaPlayedAdded(media, 0); } catch (RemoteException e) { e.printStackTrace(); } } } /** * Return the current media. * * @return The current media or null if there is not any. */ @Nullable private MediaWrapper getCurrentMedia() { return mMediaListPlayer.getMediaList().getMedia(mCurrentIndex); } /** * Alias for mCurrentIndex >= 0 * * @return True if a media is currently loaded, false otherwise */ private boolean hasCurrentMedia() { return mCurrentIndex >= 0 && mCurrentIndex < mMediaListPlayer.getMediaList().size(); } private final Handler mHandler = new AudioServiceHandler(this); private static class AudioServiceHandler extends WeakHandler<PlaybackService> { public AudioServiceHandler(PlaybackService fragment) { super(fragment); } @Override public void handleMessage(Message msg) { PlaybackService service = getOwner(); if (service == null) return; switch (msg.what) { case SHOW_PROGRESS: if (service.mCallback.size() > 0) { removeMessages(SHOW_PROGRESS); service.executeUpdateProgress(); sendEmptyMessageDelayed(SHOW_PROGRESS, 1000); } break; case SHOW_TOAST: final Bundle bundle = msg.getData(); final String text = bundle.getString("text"); final int duration = bundle.getInt("duration"); Toast.makeText(VLCApplication.getAppContext(), text, duration).show(); break; } } } @TargetApi(Build.VERSION_CODES.LOLLIPOP) private void showNotification() { try { MediaWrapper media = getCurrentMedia(); if (media == null) return; Bitmap cover = AudioUtil.getCover(this, media, 64); String title = media.getTitle(); String artist = Util.getMediaArtist(this, media); String album = Util.getMediaAlbum(this, media); Notification notification; if (media.isArtistUnknown() && media.isAlbumUnknown() && media.getNowPlaying() != null) { artist = media.getNowPlaying(); album = ""; } //Watch notification dismissed PendingIntent piStop = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_REMOTE_STOP), PendingIntent.FLAG_UPDATE_CURRENT); // add notification to status bar NotificationCompat.Builder builder = new NotificationCompat.Builder(this) .setSmallIcon(R.drawable.ic_stat_vlc).setTicker(title + " - " + artist) .setAutoCancel(!MediaPlayer().isPlaying()).setOngoing(MediaPlayer().isPlaying()) .setDeleteIntent(piStop); Intent notificationIntent = new Intent(this, MainActivity.class); notificationIntent.setAction(AudioPlayerContainerActivity.ACTION_SHOW_PLAYER); notificationIntent.addCategory(Intent.CATEGORY_LAUNCHER); notificationIntent.putExtra(START_FROM_NOTIFICATION, true); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT); if (AndroidUtil.isJellyBeanOrLater()) { Intent iBackward = new Intent(ACTION_REMOTE_BACKWARD); Intent iPlay = new Intent(ACTION_REMOTE_PLAYPAUSE); Intent iForward = new Intent(ACTION_REMOTE_FORWARD); PendingIntent piBackward = PendingIntent.getBroadcast(this, 0, iBackward, PendingIntent.FLAG_UPDATE_CURRENT); PendingIntent piPlay = PendingIntent.getBroadcast(this, 0, iPlay, PendingIntent.FLAG_UPDATE_CURRENT); PendingIntent piForward = PendingIntent.getBroadcast(this, 0, iForward, PendingIntent.FLAG_UPDATE_CURRENT); RemoteViews view = new RemoteViews(BuildConfig.APPLICATION_ID, R.layout.notification); view.setImageViewBitmap(R.id.cover, cover == null ? BitmapFactory.decodeResource(getResources(), R.drawable.icon) : cover); view.setTextViewText(R.id.songName, title); view.setTextViewText(R.id.artist, artist); view.setImageViewResource(R.id.play_pause, MediaPlayer().isPlaying() ? R.drawable.ic_pause_w : R.drawable.ic_play_w); view.setOnClickPendingIntent(R.id.play_pause, piPlay); view.setOnClickPendingIntent(R.id.forward, piForward); view.setOnClickPendingIntent(R.id.stop, piStop); view.setOnClickPendingIntent(R.id.content, pendingIntent); RemoteViews view_expanded = new RemoteViews(BuildConfig.APPLICATION_ID, R.layout.notification_expanded); view_expanded.setImageViewBitmap(R.id.cover, cover == null ? BitmapFactory.decodeResource(getResources(), R.drawable.icon) : cover); view_expanded.setTextViewText(R.id.songName, title); view_expanded.setTextViewText(R.id.artist, artist); view_expanded.setTextViewText(R.id.album, album); view_expanded.setImageViewResource(R.id.play_pause, MediaPlayer().isPlaying() ? R.drawable.ic_pause_w : R.drawable.ic_play_w); view_expanded.setOnClickPendingIntent(R.id.backward, piBackward); view_expanded.setOnClickPendingIntent(R.id.play_pause, piPlay); view_expanded.setOnClickPendingIntent(R.id.forward, piForward); view_expanded.setOnClickPendingIntent(R.id.stop, piStop); view_expanded.setOnClickPendingIntent(R.id.content, pendingIntent); if (AndroidUtil.isLolliPopOrLater()) { //Hide stop button on pause, we swipe notification to stop view.setViewVisibility(R.id.stop, MediaPlayer().isPlaying() ? View.VISIBLE : View.INVISIBLE); view_expanded.setViewVisibility(R.id.stop, MediaPlayer().isPlaying() ? View.VISIBLE : View.INVISIBLE); //Make notification appear on lockscreen builder.setVisibility(Notification.VISIBILITY_PUBLIC); } notification = builder.build(); notification.contentView = view; notification.bigContentView = view_expanded; } else { builder.setLargeIcon( cover == null ? BitmapFactory.decodeResource(getResources(), R.drawable.icon) : cover) .setContentTitle(title) .setContentText( AndroidUtil.isJellyBeanOrLater() ? artist : Util.getMediaSubtitle(this, media)) .setContentInfo(album).setContentIntent(pendingIntent); notification = builder.build(); } startService(new Intent(this, PlaybackService.class)); if (!AndroidUtil.isLolliPopOrLater() || MediaPlayer().isPlaying()) startForeground(3, notification); else { stopForeground(false); NotificationManagerCompat.from(this).notify(3, notification); } } catch (NoSuchMethodError e) { // Compat library is wrong on 3.2 // http://code.google.com/p/android/issues/detail?id=36359 // http://code.google.com/p/android/issues/detail?id=36502 } } private void hideNotification() { hideNotification(true); } /** * Hides the VLC notification and stops the service. * * @param stopPlayback True to also stop playback at the same time. Set to false to preserve playback (e.g. for vout events) */ private void hideNotification(boolean stopPlayback) { stopForeground(true); if (stopPlayback) stopSelf(); } private void pause() { setUpRemoteControlClient(); mHandler.removeMessages(SHOW_PROGRESS); // hideNotification(); <-- see event handler MediaPlayer().pause(); broadcastMetadata(); } private void play() { if (hasCurrentMedia()) { setUpRemoteControlClient(); MediaPlayer().play(); mHandler.sendEmptyMessage(SHOW_PROGRESS); showNotification(); updateWidget(); broadcastMetadata(); } } private void stop() { savePosition(); MediaPlayer().stop(); mEventHandler.removeHandler(mVlcEventHandler); mMediaListPlayer.getMediaList().removeEventListener(mListEventListener); setRemoteControlClientPlaybackState(EventHandler.MediaPlayerStopped); mCurrentIndex = -1; mPrevious.clear(); mHandler.removeMessages(SHOW_PROGRESS); hideNotification(); broadcastMetadata(); executeUpdate(); executeUpdateProgress(); changeAudioFocus(false); } private void determinePrevAndNextIndices() { determinePrevAndNextIndices(false); } private void determinePrevAndNextIndices(boolean expand) { if (expand) { mExpanding.set(true); mNextIndex = mMediaListPlayer.expand(); mExpanding.set(false); } else { mNextIndex = -1; } mPrevIndex = -1; if (mNextIndex == -1) { // No subitems; play the next item. int size = mMediaListPlayer.getMediaList().size(); mShuffling &= size > 2; // Repeating once doesn't change the index if (mRepeating == RepeatType.Once) { mPrevIndex = mNextIndex = mCurrentIndex; } else { if (mShuffling) { if (mPrevious.size() > 0) mPrevIndex = mPrevious.peek(); // If we've played all songs already in shuffle, then either // reshuffle or stop (depending on RepeatType). if (mPrevious.size() + 1 == size) { if (mRepeating == RepeatType.None) { mNextIndex = -1; return; } else { mPrevious.clear(); } } if (mRandom == null) mRandom = new Random(); // Find a new index not in mPrevious. do { mNextIndex = mRandom.nextInt(size); } while (mNextIndex == mCurrentIndex || mPrevious.contains(mNextIndex)); } else { // normal playback if (mCurrentIndex > 0) mPrevIndex = mCurrentIndex - 1; if (mCurrentIndex + 1 < size) mNextIndex = mCurrentIndex + 1; else { if (mRepeating == RepeatType.None) { mNextIndex = -1; } else { mNextIndex = 0; } } } } } } private void next() { mPrevious.push(mCurrentIndex); mCurrentIndex = mNextIndex; int size = mMediaListPlayer.getMediaList().size(); if (size == 0 || mCurrentIndex < 0 || mCurrentIndex >= size) { if (mCurrentIndex < 0) saveCurrentMedia(); Log.w(TAG, "Warning: invalid next index, aborted !"); stop(); return; } mMediaListPlayer.playIndex(this, mCurrentIndex, VLCOptions.MEDIA_NO_VIDEO); executeOnMediaPlayedAdded(); mHandler.sendEmptyMessage(SHOW_PROGRESS); setUpRemoteControlClient(); showNotification(); updateWidget(); broadcastMetadata(); updateRemoteControlClientMetadata(); saveCurrentMedia(); determinePrevAndNextIndices(); } @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) private void updateRemoteControlClientMetadata() { if (!AndroidUtil.isICSOrLater()) // NOP check return; MediaWrapper media = getCurrentMedia(); if (mRemoteControlClient != null && media != null) { MetadataEditor editor = mRemoteControlClient.editMetadata(true); if (media.getNowPlaying() != null) { editor.putString(MediaMetadataRetriever.METADATA_KEY_ALBUM, ""); editor.putString(MediaMetadataRetriever.METADATA_KEY_ARTIST, ""); editor.putString(MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST, media.getNowPlaying()); } else { editor.putString(MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST, ""); editor.putString(MediaMetadataRetriever.METADATA_KEY_ALBUM, Util.getMediaAlbum(this, media)); editor.putString(MediaMetadataRetriever.METADATA_KEY_ARTIST, Util.getMediaArtist(this, media)); } editor.putString(MediaMetadataRetriever.METADATA_KEY_GENRE, Util.getMediaGenre(this, media)); editor.putString(MediaMetadataRetriever.METADATA_KEY_TITLE, media.getTitle()); editor.putLong(MediaMetadataRetriever.METADATA_KEY_DURATION, media.getLength()); // Copy the cover bitmap because the RemonteControlClient can recycle its artwork bitmap. Bitmap cover = AudioUtil.getCover(this, media, 512); if (cover != null && cover.getConfig() != null) //In case of format not supported editor.putBitmap(MetadataEditor.BITMAP_KEY_ARTWORK, (cover.copy(cover.getConfig(), false))); editor.apply(); } //Send metadata to Pebble watch if (media != null && mPebbleEnabled) { final Intent i = new Intent("com.getpebble.action.NOW_PLAYING"); i.putExtra("artist", Util.getMediaArtist(this, media)); i.putExtra("album", Util.getMediaAlbum(this, media)); i.putExtra("track", media.getTitle()); sendBroadcast(i); } } private void previous() { mCurrentIndex = mPrevIndex; if (mPrevious.size() > 0) mPrevious.pop(); int size = mMediaListPlayer.getMediaList().size(); if (size == 0 || mPrevIndex < 0 || mCurrentIndex >= size) { Log.w(TAG, "Warning: invalid previous index, aborted !"); stop(); return; } mMediaListPlayer.playIndex(this, mCurrentIndex, VLCOptions.MEDIA_NO_VIDEO); executeOnMediaPlayedAdded(); mHandler.sendEmptyMessage(SHOW_PROGRESS); setUpRemoteControlClient(); showNotification(); updateWidget(); broadcastMetadata(); updateRemoteControlClientMetadata(); saveCurrentMedia(); determinePrevAndNextIndices(); } private void shuffle() { if (mShuffling) mPrevious.clear(); mShuffling = !mShuffling; saveCurrentMedia(); determinePrevAndNextIndices(); } private void setRepeatType(int t) { mRepeating = RepeatType.values()[t]; saveCurrentMedia(); determinePrevAndNextIndices(); } private void updateWidget() { updateWidgetState(); updateWidgetCover(); } private void updateWidgetState() { Intent i = new Intent(this, VLCAppWidgetProvider.class); i.setAction(ACTION_WIDGET_UPDATE); if (hasCurrentMedia()) { final MediaWrapper media = getCurrentMedia(); i.putExtra("title", media.getTitle()); i.putExtra("artist", media.isArtistUnknown() && media.getNowPlaying() != null ? media.getNowPlaying() : Util.getMediaArtist(this, media)); } else { i.putExtra("title", getString(R.string.widget_name)); i.putExtra("artist", ""); } i.putExtra("isplaying", MediaPlayer().isPlaying()); sendBroadcast(i); } private void updateWidgetCover() { Intent i = new Intent(this, VLCAppWidgetProvider.class); i.setAction(ACTION_WIDGET_UPDATE_COVER); Bitmap cover = hasCurrentMedia() ? AudioUtil.getCover(this, getCurrentMedia(), 64) : null; i.putExtra("cover", cover); sendBroadcast(i); } private void updateWidgetPosition(float pos) { // no more than one widget update for each 1/50 of the song long timestamp = Calendar.getInstance().getTimeInMillis(); if (!hasCurrentMedia() || timestamp - mWidgetPositionTimestamp < getCurrentMedia().getLength() / 50) return; updateWidgetState(); mWidgetPositionTimestamp = timestamp; Intent i = new Intent(this, VLCAppWidgetProvider.class); i.setAction(ACTION_WIDGET_UPDATE_POSITION); i.putExtra("position", pos); sendBroadcast(i); } private void broadcastMetadata() { MediaWrapper media = getCurrentMedia(); if (media == null || media.getType() != MediaWrapper.TYPE_AUDIO) return; boolean playing = MediaPlayer().isPlaying(); Intent broadcast = new Intent("com.android.music.metachanged"); broadcast.putExtra("track", media.getTitle()); broadcast.putExtra("artist", media.getArtist()); broadcast.putExtra("album", media.getAlbum()); broadcast.putExtra("duration", media.getLength()); broadcast.putExtra("playing", playing); sendBroadcast(broadcast); } private synchronized void loadLastPlaylist() { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); String currentMedia = prefs.getString("current_media", ""); if (currentMedia.equals("")) return; String[] locations = prefs.getString("media_list", "").split(" "); List<String> mediaPathList = new ArrayList<String>(locations.length); for (int i = 0; i < locations.length; ++i) mediaPathList.add(Uri.decode(locations[i])); mShuffling = prefs.getBoolean("shuffling", false); mRepeating = RepeatType.values()[prefs.getInt("repeating", RepeatType.None.ordinal())]; int position = prefs.getInt("position_in_list", Math.max(0, mediaPathList.indexOf(currentMedia))); long time = prefs.getLong("position_in_song", -1); // load playlist try { mInterface.loadLocations(mediaPathList, position); if (time > 0) mInterface.setTime(time); } catch (RemoteException e) { e.printStackTrace(); } finally { SharedPreferences.Editor editor = prefs.edit(); editor.putInt("position_in_list", 0); editor.putLong("position_in_song", 0); Util.commitPreferences(editor); } } private synchronized void saveCurrentMedia() { SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(this).edit(); editor.putString("current_media", mMediaListPlayer.getMediaList().getMRL(Math.max(mCurrentIndex, 0))); editor.putBoolean("shuffling", mShuffling); editor.putInt("repeating", mRepeating.ordinal()); Util.commitPreferences(editor); } private synchronized void saveMediaList() { StringBuilder locations = new StringBuilder(); for (int i = 0; i < mMediaListPlayer.getMediaList().size(); i++) locations.append(" ").append(Uri.encode(mMediaListPlayer.getMediaList().getMRL(i))); //We save a concatenated String because putStringSet is APIv11. SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(this).edit(); editor.putString("media_list", locations.toString().trim()); Util.commitPreferences(editor); } private synchronized void savePosition() { SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(this).edit(); editor.putInt("position_in_list", mCurrentIndex); editor.putLong("position_in_song", MediaPlayer().getTime()); Util.commitPreferences(editor); } private boolean validateLocation(String location) { /* Check if the MRL contains a scheme */ if (!location.matches("\\w+://.+")) location = "file://".concat(location); if (location.toLowerCase(Locale.ENGLISH).startsWith("file://")) { /* Ensure the file exists */ File f; try { f = new File(new URI(location)); } catch (URISyntaxException e) { return false; } catch (IllegalArgumentException e) { return false; } if (!f.isFile()) return false; } return true; } private void showToast(String text, int duration) { Message msg = new Message(); Bundle bundle = new Bundle(); bundle.putString("text", text); bundle.putInt("duration", duration); msg.setData(bundle); msg.what = SHOW_TOAST; mHandler.sendMessage(msg); } private final IPlaybackService.Stub mInterface = new IPlaybackService.Stub() { @Override public void pause() throws RemoteException { PlaybackService.this.pause(); } @Override public void play() throws RemoteException { PlaybackService.this.play(); } @Override public void stop() throws RemoteException { PlaybackService.this.stop(); } @Override public boolean isPlaying() throws RemoteException { return MediaPlayer().isPlaying(); } @Override public boolean isShuffling() { return mShuffling; } @Override public int getRepeatType() { return mRepeating.ordinal(); } @Override public boolean hasMedia() throws RemoteException { return hasCurrentMedia(); } @Override public String getAlbum() throws RemoteException { if (hasCurrentMedia()) return Util.getMediaAlbum(PlaybackService.this, getCurrentMedia()); else return null; } @Override public String getArtist() throws RemoteException { if (hasCurrentMedia()) { final MediaWrapper media = getCurrentMedia(); return media.isArtistUnknown() && media.getNowPlaying() != null ? media.getNowPlaying() : Util.getMediaArtist(PlaybackService.this, media); } else return null; } @Override public String getArtistPrev() throws RemoteException { if (mPrevIndex != -1) return Util.getMediaArtist(PlaybackService.this, mMediaListPlayer.getMediaList().getMedia(mPrevIndex)); else return null; } @Override public String getArtistNext() throws RemoteException { if (mNextIndex != -1) return Util.getMediaArtist(PlaybackService.this, mMediaListPlayer.getMediaList().getMedia(mNextIndex)); else return null; } @Override public String getTitle() throws RemoteException { if (hasCurrentMedia()) return getCurrentMedia().getTitle(); else return null; } @Override public String getTitlePrev() throws RemoteException { if (mPrevIndex != -1) return mMediaListPlayer.getMediaList().getMedia(mPrevIndex).getTitle(); else return null; } @Override public String getTitleNext() throws RemoteException { if (mNextIndex != -1) return mMediaListPlayer.getMediaList().getMedia(mNextIndex).getTitle(); else return null; } @Override public Bitmap getCover() { if (hasCurrentMedia()) { return AudioUtil.getCover(PlaybackService.this, getCurrentMedia(), 512); } return null; } @Override public Bitmap getCoverPrev() throws RemoteException { if (mPrevIndex != -1) return AudioUtil.getCover(PlaybackService.this, mMediaListPlayer.getMediaList().getMedia(mPrevIndex), 64); else return null; } @Override public Bitmap getCoverNext() throws RemoteException { if (mNextIndex != -1) return AudioUtil.getCover(PlaybackService.this, mMediaListPlayer.getMediaList().getMedia(mNextIndex), 64); else return null; } @Override public synchronized void addAudioCallback(IPlaybackServiceCallback cb) throws RemoteException { Integer count = mCallback.get(cb); if (count == null) count = 0; mCallback.put(cb, count + 1); if (hasCurrentMedia()) mHandler.sendEmptyMessage(SHOW_PROGRESS); } @Override public synchronized void removeAudioCallback(IPlaybackServiceCallback cb) throws RemoteException { Integer count = mCallback.get(cb); if (count == null) count = 0; if (count > 1) mCallback.put(cb, count - 1); else mCallback.remove(cb); } @Override public int getTime() throws RemoteException { return (int) MediaPlayer().getTime(); } @Override public int getLength() throws RemoteException { return (int) MediaPlayer().getLength(); } /** * Loads a selection of files (a non-user-supplied collection of media) * into the primary or "currently playing" playlist. * * @param mediaPathList A list of locations to load * @param position The position to start playing at * @throws RemoteException */ @Override public void loadLocations(List<String> mediaPathList, int position) throws RemoteException { ArrayList<MediaWrapper> mediaList = new ArrayList<MediaWrapper>(); MediaDatabase db = MediaDatabase.getInstance(); for (int i = 0; i < mediaPathList.size(); i++) { String location = mediaPathList.get(i); MediaWrapper mediaWrapper = db.getMedia(Uri.parse(location)); if (mediaWrapper == null) { if (!validateLocation(location)) { Log.w(TAG, "Invalid location " + location); showToast(getResources().getString(R.string.invalid_location, location), Toast.LENGTH_SHORT); continue; } Log.v(TAG, "Creating on-the-fly Media object for " + location); final Media media = new Media(LibVLC(), Uri.parse(location)); media.parse(); // FIXME: parse should be done asynchronously media.release(); mediaWrapper = new MediaWrapper(media); } mediaList.add(mediaWrapper); } load(mediaList, position, false); } @Override public void load(List<MediaWrapper> mediaList, int position, boolean forceAudio) throws RemoteException { Log.v(TAG, "Loading position " + ((Integer) position).toString() + " in " + mediaList.toString()); mEventHandler.addHandler(mVlcEventHandler); mMediaListPlayer.getMediaList().removeEventListener(mListEventListener); mMediaListPlayer.getMediaList().clear(); MediaWrapperList currentMediaList = mMediaListPlayer.getMediaList(); mPrevious.clear(); mForceAudio = forceAudio; for (int i = 0; i < mediaList.size(); i++) { currentMediaList.add(mediaList.get(i)); } if (mMediaListPlayer.getMediaList().size() == 0) { Log.w(TAG, "Warning: empty media list, nothing to play !"); return; } if (mMediaListPlayer.getMediaList().size() > position && position >= 0) { mCurrentIndex = position; } else { Log.w(TAG, "Warning: positon " + position + " out of bounds"); mCurrentIndex = 0; } // Add handler after loading the list mMediaListPlayer.getMediaList().addEventListener(mListEventListener); mMediaListPlayer.playIndex(PlaybackService.this, mCurrentIndex, VLCOptions.MEDIA_NO_VIDEO); executeOnMediaPlayedAdded(); mHandler.sendEmptyMessage(SHOW_PROGRESS); setUpRemoteControlClient(); showNotification(); updateWidget(); broadcastMetadata(); updateRemoteControlClientMetadata(); PlaybackService.this.saveMediaList(); PlaybackService.this.saveCurrentMedia(); determinePrevAndNextIndices(); } /** * Use this function to play a media inside whatever MediaList LibVLC is following. * * Unlike load(), it does not import anything into the primary list. */ @Override public void playIndex(int index) { if (mMediaListPlayer.getMediaList().size() == 0) { Log.w(TAG, "Warning: empty media list, nothing to play !"); return; } if (index >= 0 && index < mMediaListPlayer.getMediaList().size()) { mCurrentIndex = index; } else { Log.w(TAG, "Warning: index " + index + " out of bounds"); mCurrentIndex = 0; } mEventHandler.addHandler(mVlcEventHandler); mMediaListPlayer.playIndex(PlaybackService.this, mCurrentIndex, VLCOptions.MEDIA_NO_VIDEO); executeOnMediaPlayedAdded(); mHandler.sendEmptyMessage(SHOW_PROGRESS); setUpRemoteControlClient(); showNotification(); updateWidget(); broadcastMetadata(); updateRemoteControlClientMetadata(); determinePrevAndNextIndices(); } /** * Use this function to show an URI in the audio interface WITHOUT * interrupting the stream. * * Mainly used by VideoPlayerActivity in response to loss of video track. */ @Override public void showWithoutParse(int index) throws RemoteException { String URI = mMediaListPlayer.getMediaList().getMRL(index); Log.v(TAG, "Showing index " + index + " with playing URI " + URI); // Show an URI without interrupting/losing the current stream if (URI == null || !MediaPlayer().isPlaying()) return; mEventHandler.addHandler(mVlcEventHandler); mCurrentIndex = index; // Notify everyone mHandler.sendEmptyMessage(SHOW_PROGRESS); showNotification(); determinePrevAndNextIndices(); executeUpdate(); executeUpdateProgress(); } /** * Append to the current existing playlist */ @Override public void append(List<MediaWrapper> mediaList) throws RemoteException { if (!hasCurrentMedia()) { load(mediaList, 0, false); return; } for (int i = 0; i < mediaList.size(); i++) { MediaWrapper mediaWrapper = mediaList.get(i); mMediaListPlayer.getMediaList().add(mediaWrapper); } PlaybackService.this.saveMediaList(); determinePrevAndNextIndices(); executeUpdate(); } /** * Move an item inside the playlist. */ @Override public void moveItem(int positionStart, int positionEnd) throws RemoteException { mMediaListPlayer.getMediaList().move(positionStart, positionEnd); PlaybackService.this.saveMediaList(); } @Override public void remove(int position) { mMediaListPlayer.getMediaList().remove(position); PlaybackService.this.saveMediaList(); determinePrevAndNextIndices(); executeUpdate(); } @Override public void removeLocation(String location) { mMediaListPlayer.getMediaList().remove(location); PlaybackService.this.saveMediaList(); determinePrevAndNextIndices(); executeUpdate(); } @Override public List<MediaWrapper> getMedias() { final ArrayList<MediaWrapper> ml = new ArrayList<MediaWrapper>(); for (int i = 0; i < mMediaListPlayer.getMediaList().size(); i++) { ml.add(mMediaListPlayer.getMediaList().getMedia(i)); } return ml; } @Override public List<String> getMediaLocations() { ArrayList<String> medias = new ArrayList<String>(); for (int i = 0; i < mMediaListPlayer.getMediaList().size(); i++) { medias.add(mMediaListPlayer.getMediaList().getMRL(i)); } return medias; } @Override public String getCurrentMediaLocation() throws RemoteException { return mMediaListPlayer.getMediaList().getMRL(mCurrentIndex); } @Override public MediaWrapper getCurrentMediaWrapper() throws RemoteException { return PlaybackService.this.getCurrentMedia(); } @Override public void next() throws RemoteException { PlaybackService.this.next(); } @Override public void previous() throws RemoteException { PlaybackService.this.previous(); } @Override public void shuffle() throws RemoteException { PlaybackService.this.shuffle(); } @Override public void setRepeatType(int t) throws RemoteException { PlaybackService.this.setRepeatType(t); } @Override public void setTime(long time) throws RemoteException { MediaPlayer().setTime(time); } @Override public boolean hasNext() throws RemoteException { return mNextIndex != -1; } @Override public boolean hasPrevious() throws RemoteException { return mPrevIndex != -1; } @Override public void detectHeadset(boolean enable) throws RemoteException { mDetectHeadset = enable; } @Override public float getRate() throws RemoteException { return MediaPlayer().getRate(); } @Override public void handleVout() throws RemoteException { PlaybackService.this.handleVout(); } }; }