[Android] [PATCH 5/6] PlaybackServiceClient: no more static client, no more reflection

Thomas Guillem thomas at gllm.fr
Thu Jun 11 17:45:43 CEST 2015


---
 .../src/org/videolan/vlc/PlaybackService.java      |    3 +
 .../org/videolan/vlc/PlaybackServiceClient.java    | 1001 ++++++++++++++------
 .../vlc/gui/AudioPlayerContainerActivity.java      |   43 +-
 .../src/org/videolan/vlc/gui/HistoryAdapter.java   |   18 +-
 .../src/org/videolan/vlc/gui/HistoryFragment.java  |   10 +-
 .../src/org/videolan/vlc/gui/MainActivity.java     |    1 -
 .../org/videolan/vlc/gui/PreferencesActivity.java  |   35 +-
 .../videolan/vlc/gui/audio/AudioAlbumFragment.java |   17 +-
 .../vlc/gui/audio/AudioAlbumsSongsFragment.java    |   31 +-
 .../vlc/gui/audio/AudioBrowserFragment.java        |   45 +-
 .../org/videolan/vlc/gui/audio/AudioPlayer.java    |  133 ++-
 .../vlc/gui/browser/BaseBrowserFragment.java       |   11 +-
 .../videolan/vlc/gui/video/VideoGridFragment.java  |   27 +-
 .../vlc/gui/video/VideoPlayerActivity.java         |   84 +-
 vlc-android/src/org/videolan/vlc/util/Util.java    |   17 +-
 .../videolan/vlc/widget/AudioMediaSwitcher.java    |   32 +-
 .../vlc/gui/tv/MediaItemDetailsFragment.java       |   41 +-
 .../gui/tv/audioplayer/AudioPlayerActivity.java    |   96 +-
 18 files changed, 1059 insertions(+), 586 deletions(-)

diff --git a/vlc-android/src/org/videolan/vlc/PlaybackService.java b/vlc-android/src/org/videolan/vlc/PlaybackService.java
index 7073408..7420dd6 100644
--- a/vlc-android/src/org/videolan/vlc/PlaybackService.java
+++ b/vlc-android/src/org/videolan/vlc/PlaybackService.java
@@ -169,6 +169,9 @@ public class PlaybackService extends Service {
             return;
         }
 
+        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
+        mDetectHeadset = prefs.getBoolean("enable_headset_detection", true);
+
         mMediaListPlayer = MediaWrapperListPlayer.getInstance();
 
         mCallback = new HashMap<IPlaybackServiceCallback, Integer>();
diff --git a/vlc-android/src/org/videolan/vlc/PlaybackServiceClient.java b/vlc-android/src/org/videolan/vlc/PlaybackServiceClient.java
index 9442cbf..582f33c 100644
--- a/vlc-android/src/org/videolan/vlc/PlaybackServiceClient.java
+++ b/vlc-android/src/org/videolan/vlc/PlaybackServiceClient.java
@@ -24,466 +24,863 @@ import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
-import android.content.SharedPreferences;
 import android.graphics.Bitmap;
 import android.os.IBinder;
 import android.os.RemoteException;
-import android.preference.PreferenceManager;
 import android.util.Log;
 
 import org.videolan.vlc.interfaces.IPlaybackService;
 import org.videolan.vlc.interfaces.IPlaybackServiceCallback;
 
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.List;
 
-public class PlaybackServiceClient {
+public class PlaybackServiceClient implements ServiceConnection {
     public static final String TAG = "PlaybackServiceClient";
 
     public interface Callback {
+        void onConnected();
+        void onDisconnected();
         void update();
         void updateProgress();
         void onMediaPlayedAdded(MediaWrapper media, int index);
         void onMediaPlayedRemoved(int index);
     }
 
-    private static PlaybackServiceClient mInstance;
-    private static boolean mIsBound = false;
-    private IPlaybackService mIService;
-    private ServiceConnection mServiceConnection;
-    private final ArrayList<Callback> mCallbacks;
+    public interface AsyncCallback<T> {
+        void onResult(PlaybackServiceClient client, T result);
+        void onError(PlaybackServiceClient client);
+    }
+
+    private boolean mBound = false;
+    private IPlaybackService mIService = null;
+    private final Callback mCallback;
+    private final Context mContext;
 
-    private final IPlaybackServiceCallback mCallback = new IPlaybackServiceCallback.Stub() {
+    private final IPlaybackServiceCallback mICallback = new IPlaybackServiceCallback.Stub() {
         @Override
         public void update() throws RemoteException {
-            updateAudioPlayer();
+            mCallback.update();
         }
 
         @Override
         public void updateProgress() throws RemoteException {
-            updateProgressAudioPlayer();
+            mCallback.updateProgress();
         }
 
         @Override
         public void onMediaPlayedAdded(MediaWrapper media, int index) throws RemoteException {
-            updateMediaPlayedAdded(media, index);
+            mCallback.onMediaPlayedAdded(media, index);
         }
 
         @Override
         public void onMediaPlayedRemoved(int index) throws RemoteException {
-            updateMediaPlayedRemoved(index);
+            mCallback.onMediaPlayedRemoved(index);
         }
     };
 
-    private PlaybackServiceClient() {
-        mCallbacks = new ArrayList<Callback>();
+    @Override
+    public void onServiceDisconnected(ComponentName name) {
+        Log.d(TAG, "Service Disconnected");
+        onDisconnected(false);
     }
 
-    public static PlaybackServiceClient getInstance() {
-        if (mInstance == null) {
-            mInstance = new PlaybackServiceClient();
+    @Override
+    public void onServiceConnected(ComponentName name, IBinder service) {
+        Log.d(TAG, "Service Connected");
+        if (!mBound)
+            return;
+        mIService = IPlaybackService.Stub.asInterface(service);
+
+        if (mCallback != null) {
+            try {
+                mIService.addAudioCallback(mICallback);
+                mCallback.onConnected();
+                mCallback.update();
+            } catch (RemoteException e) {
+                Log.e(TAG, "remote procedure send failed: addAudioCallback()");
+                onDisconnected(true);
+            }
         }
-        return mInstance;
     }
 
-    /**
-     * The connection listener interface for the audio service
-     */
-    public interface AudioServiceConnectionListener {
-        public void onConnectionSuccess();
-        public void onConnectionFailed();
+    private static Intent getServiceIntent(Context context) {
+        return new Intent(context, PlaybackService.class);
     }
 
-    /**
-     * Bind to audio service if it is running
-     */
-    public void bindAudioService(Context context) {
-        bindAudioService(context, null);
+    private static void startService(Context context) {
+        context.startService(getServiceIntent(context));
     }
 
-    public void bindAudioService(Context context, final AudioServiceConnectionListener connectionListerner) {
-        if (context == null) {
-            Log.w(TAG, "bindAudioService() with null Context. Ooops" );
-            return;
+    private static void stopService(Context context) {
+        context.stopService(getServiceIntent(context));
+    }
+
+    public PlaybackServiceClient(Context context, Callback callback) {
+        if (context == null)
+            throw new IllegalArgumentException("Context can't be null");
+        mContext = context;
+        mCallback = callback;
+    }
+
+    public void connect() {
+        if (mBound)
+            throw new IllegalStateException("already connected");
+        startService(mContext);
+        mBound = mContext.bindService(getServiceIntent(mContext), this, Context.BIND_AUTO_CREATE);
+    }
+
+    public void disconnect() {
+        if (mBound) {
+            try {
+                if (mIService != null && mCallback != null)
+                    mIService.removeAudioCallback(mICallback);
+            } catch (RemoteException e) {
+                Log.e(TAG, "remote procedure send failed: removeAudioCallback()");
+            }
+            mIService = null;
+            mBound = false;
+            mContext.unbindService(this);
         }
-        context = context.getApplicationContext();
+    }
 
-        if (!mIsBound) {
-            Intent service = new Intent(context, PlaybackService.class);
+    private void onDisconnected(boolean error) {
+        if (error && mBound && mCallback != null)
+            mCallback.onDisconnected();
+        disconnect();
 
-            SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
-            final boolean enableHS = prefs.getBoolean("enable_headset_detection", true);
+        if (error)
+            stopService(mContext);
+        else
+            connect();
+    }
 
-            // Setup audio service connection
-            mServiceConnection = new ServiceConnection() {
-                @Override
-                public void onServiceDisconnected(ComponentName name) {
-                    Log.d(TAG, "Service Disconnected");
-                    mIService = null;
-                    mIsBound = false;
-                }
+    public void restartService() {
+        disconnect();
+        stopService(mContext);
+        startService(mContext);
+        connect();
+    }
 
-                @Override
-                public void onServiceConnected(ComponentName name, IBinder service) {
-                    if (!mIsBound) // Can happen if unbind is called quickly before this callback
-                        return;
-                    Log.d(TAG, "Service Connected");
-                    mIService = IPlaybackService.Stub.asInterface(service);
+    public static void restartService(Context context) {
+        stopService(context);
+        startService(context);
+    }
+
+    public boolean isConnected() {
+        return mBound && mIService != null;
+    }
+
+    private static abstract class Command<T> {
+        public Command() {
+        }
+
+        protected abstract T run(IPlaybackService iService) throws RemoteException;
+
+        public T send(IPlaybackService iService, T defaultValue) {
+            if (iService == null)
+                throw new IllegalStateException("can't send remote methods without being connected");
+            try {
+                return run(iService);
+            } catch (RemoteException e) {
+                Log.e(TAG, "remote send failed", e);
+                return defaultValue;
+            }
+        }
+
+        public T send(IPlaybackService iService) {
+            return send(iService, null);
+        }
+
+        public void sendAsync(Context context, final AsyncCallback<T> asyncCb) {
+            class Holder {
+                PlaybackServiceClient client;
+            }
+
+            final Holder holder = new Holder();
+            holder.client = new PlaybackServiceClient(context, new PlaybackServiceClient.Callback() {
 
-                    // Register controller to the service
+                @Override
+                public void onConnected() {
                     try {
-                        mIService.addAudioCallback(mCallback);
-                        mIService.detectHeadset(enableHS);
-                        if (connectionListerner != null)
-                            connectionListerner.onConnectionSuccess();
+                        final T result = run(holder.client.mIService);
+                        if (asyncCb != null)
+                            asyncCb.onResult(holder.client, result);
                     } catch (RemoteException e) {
-                        Log.e(TAG, "remote procedure call failed: addAudioCallback()");
-                        if (connectionListerner != null)
-                            connectionListerner.onConnectionFailed();
+                        if (asyncCb != null)
+                            asyncCb.onError(holder.client);
                     }
-                    updateAudioPlayer();
+                    holder.client.disconnect();
+                    holder.client = null;
                 }
-            };
 
-            mIsBound = context.bindService(service, mServiceConnection, Context.BIND_AUTO_CREATE);
-        } else {
-            // Register controller to the service
-            try {
-                if (mIService != null)
-                    mIService.addAudioCallback(mCallback);
-                if (connectionListerner != null)
-                    connectionListerner.onConnectionSuccess();
-            } catch (RemoteException e) {
-                Log.e(TAG, "remote procedure call failed: addAudioCallback()");
-                if (connectionListerner != null)
-                    connectionListerner.onConnectionFailed();
-            }
+                @Override
+                public void onDisconnected() {
+                    if (asyncCb != null)
+                        asyncCb.onError(holder.client);
+                    holder.client.disconnect();
+                    holder.client = null;
+                }
+
+                @Override
+                public void update() {}
+                @Override
+                public void updateProgress() {}
+                @Override
+                public void onMediaPlayedAdded(MediaWrapper media, int index) {}
+                @Override
+                public void onMediaPlayedRemoved(int index) {}
+            });
+            holder.client.connect();
         }
     }
 
-    public void unbindAudioService(Context context) {
-        if (context == null) {
-            Log.w(TAG, "unbindAudioService() with null Context. Ooops" );
-            return;
+    private static class LoadCmd extends Command<Void> {
+        final List<MediaWrapper> mediaList; final int position; final boolean forceAudio;
+
+        private LoadCmd(List<MediaWrapper> mediaList, int position, boolean forceAudio) {
+            this.mediaList = mediaList;
+            this.position = position;
+            this.forceAudio = forceAudio;
         }
-        context = context.getApplicationContext();
 
-        if (mIsBound) {
-            mIsBound = false;
-            try {
-                if (mIService != null)
-                    mIService.removeAudioCallback(mCallback);
-            } catch (RemoteException e) {
-                Log.e(TAG, "remote procedure call failed: removeAudioCallback()");
-            }
-            context.unbindService(mServiceConnection);
-            mIService = null;
-            mServiceConnection = null;
-        }
-    }
-
-    /**
-     * Add a Callback
-     * @param callback
-     */
-    public void addCallback(Callback callback) {
-        if (!mCallbacks.contains(callback))
-            mCallbacks.add(callback);
-    }
-
-    /**
-     * Remove Callback from list
-     * @param callback
-     */
-    public void removeCallback(Callback callback) {
-        if (mCallbacks.contains(callback))
-            mCallbacks.remove(callback);
-    }
-
-    /**
-     * Update all AudioPlayer
-     */
-    private void updateAudioPlayer() {
-        for (Callback player : mCallbacks)
-            player.update();
-    }
-
-    /**
-     * Update the progress of all AudioPlayers
-     */
-    private void updateProgressAudioPlayer() {
-        for (Callback player : mCallbacks)
-            player.updateProgress();
-    }
-
-    private void updateMediaPlayedAdded(MediaWrapper media, int index) {
-        for (Callback listener : mCallbacks) {
-            listener.onMediaPlayedAdded(media, index);
-        }
-    }
-
-    private void updateMediaPlayedRemoved(int index) {
-        for (Callback listener : mCallbacks) {
-            listener.onMediaPlayedRemoved(index);
-        }
-    }
-
-    /**
-     * This is a handy utility function to call remote procedure calls from mIService
-     * to reduce code duplication across methods of AudioServiceController.
-     *
-     * @param instance The instance of IPlaybackService to call, usually mIService
-     * @param returnType Return type of the method being called
-     * @param defaultValue Default value to return in case of null or exception
-     * @param functionName The function name to call, e.g. "stop"
-     * @param parameterTypes List of parameter types. Pass null if none.
-     * @param parameters List of parameters. Must be in same order as parameterTypes. Pass null if none.
-     * @return The results of the RPC or defaultValue if error
-     */
-    private <T> T remoteProcedureCall(IPlaybackService instance, Class<T> returnType, T defaultValue, String functionName, Class<?> parameterTypes[], Object parameters[]) {
-        if(instance == null) {
-            return defaultValue;
-        }
-
-        try {
-            Method m = IPlaybackService.class.getMethod(functionName, parameterTypes);
-            @SuppressWarnings("unchecked")
-            T returnVal = (T) m.invoke(instance, parameters);
-            return returnVal;
-        } catch(NoSuchMethodException e) {
-            e.printStackTrace();
-            return defaultValue;
-        } catch (IllegalArgumentException e) {
-            e.printStackTrace();
-            return defaultValue;
-        } catch (IllegalAccessException e) {
-            e.printStackTrace();
-            return defaultValue;
-        } catch (InvocationTargetException e) {
-            if(e.getTargetException() instanceof RemoteException) {
-                Log.e(TAG, "remote procedure call failed: " + functionName + "()");
-            }
-            return defaultValue;
+        @Override
+        protected Void run(IPlaybackService iService) throws RemoteException {
+            iService.load(mediaList, position, forceAudio); return null;
         }
     }
 
-    public void loadLocation(String mediaPath) {
-        ArrayList < String > arrayList = new ArrayList<String>();
-        arrayList.add(mediaPath);
-        loadLocations(arrayList, 0);
+    private static class LoadLocationsCmd extends Command<Void> {
+        final List<String> mediaPathList; final int position;
+
+        private LoadLocationsCmd(List<String> mediaPathList, int position) {
+            this.mediaPathList = mediaPathList;
+            this.position = position;
+        }
+
+        @Override
+        protected Void run(IPlaybackService iService) throws RemoteException {
+            iService.loadLocations(mediaPathList, position); return null;
+        }
     }
 
+    private static class AppendCmd extends Command<Void> {
+        final List<MediaWrapper> mediaList;
 
-    public void load(MediaWrapper media, boolean forceAudio) {
-        ArrayList<MediaWrapper> arrayList = new ArrayList<MediaWrapper>();
-        arrayList.add(media);
-        load(arrayList, 0, forceAudio);
+        private AppendCmd(List<MediaWrapper> mediaList) {
+            this.mediaList = mediaList;
+        }
+
+        @Override
+        protected Void run(IPlaybackService iService) throws RemoteException {
+            iService.append(mediaList); return null;
+        }
     }
 
-    public void load(MediaWrapper media) {
-        load(media, false);
+    private static class MoveItemCmd extends Command<Void> {
+        final int positionStart, positionEnd;
+
+        private MoveItemCmd(int positionStart, int positionEnd) {
+            this.positionStart = positionStart;
+            this.positionEnd = positionEnd;
+        }
+
+        @Override
+        protected Void run(IPlaybackService iService) throws RemoteException {
+            iService.moveItem(positionStart, positionEnd); return null;
+        }
     }
 
-    public void loadLocations(List<String> mediaPathList, int position) {
-        remoteProcedureCall(mIService, Void.class, (Void) null, "loadLocations",
-                new Class<?>[]{List.class, int.class},
-                new Object[]{mediaPathList, position});
+    private static class RemoveCmd extends Command<Void> {
+        final int position;
+
+        private RemoveCmd(int position) {
+            this.position = position;
+        }
+
+        @Override
+        protected Void run(IPlaybackService iService) throws RemoteException {
+            iService.remove(position); return null;
+        }
+    }
+
+    private static class RemoveLocationCmd extends Command<Void> {
+        final String location;
+
+        private RemoveLocationCmd(String location) {
+            this.location = location;
+        }
+
+        @Override
+        protected Void run(IPlaybackService iService) throws RemoteException {
+            iService.removeLocation(location); return null;
+        }
+    }
+
+    private static class GetMediasCmd extends Command<List<MediaWrapper>> {
+        @Override
+        protected List<MediaWrapper> run(IPlaybackService iService) throws RemoteException {
+            return iService.getMedias();
+        }
+    }
+
+    private static class GetMediaLocationsCmd extends Command<List<String>> {
+        @Override
+        protected List<String> run(IPlaybackService iService) throws RemoteException {
+            return iService.getMediaLocations();
+        }
+    }
+
+    private static class GetCurrentMediaLocationCmd extends Command<String> {
+        @Override
+        protected String run(IPlaybackService iService) throws RemoteException {
+            return iService.getCurrentMediaLocation();
+        }
+    }
+
+    private static class GetCurrentMediaWrapperCmd extends Command<MediaWrapper> {
+        @Override
+        protected MediaWrapper run(IPlaybackService iService) throws RemoteException {
+            return iService.getCurrentMediaWrapper();
+        }
+    }
+
+    private static class StopCmd extends Command<Void> {
+        @Override
+        protected Void run(IPlaybackService iService) throws RemoteException {
+            iService.stop(); return null;
+        }
+    }
+
+    private static class ShowWithoutParseCmd extends Command<Void> {
+        final int index;
+
+        private ShowWithoutParseCmd(int index) {
+            this.index = index;
+        }
+
+        @Override
+        protected Void run(IPlaybackService iService) throws RemoteException {
+            iService.showWithoutParse(index); return null;
+        }
+    }
+
+    private static class PlayIndexCmd extends Command<Void> {
+        final int index;
+
+        private PlayIndexCmd(int index) {
+            this.index = index;
+        }
+
+        @Override
+        protected Void run(IPlaybackService iService) throws RemoteException {
+            iService.playIndex(index); return null;
+        }
+    }
+
+    private static class GetAlbumCmd extends Command<String> {
+        @Override
+        protected String run(IPlaybackService iService) throws RemoteException {
+            return iService.getAlbum();
+        }
+    }
+
+    private static class GetArtistCmd extends Command<String> {
+        @Override
+        protected String run(IPlaybackService iService) throws RemoteException {
+            return iService.getArtist();
+        }
+    }
+
+    private static class GetArtistPrevCmd extends Command<String> {
+        @Override
+        protected String run(IPlaybackService iService) throws RemoteException {
+            return iService.getArtistPrev();
+        }
+    }
+
+    private static class GetArtistNextCmd extends Command<String> {
+        @Override
+        protected String run(IPlaybackService iService) throws RemoteException {
+            return iService.getArtistNext();
+        }
+    }
+
+    private static class GetTitleCmd extends Command<String> {
+        @Override
+        protected String run(IPlaybackService iService) throws RemoteException {
+            return iService.getTitle();
+        }
+    }
+
+    private static class GetTitlePrevCmd extends Command<String> {
+        @Override
+        protected String run(IPlaybackService iService) throws RemoteException {
+            return iService.getTitlePrev();
+        }
+    }
+
+    private static class GetTitleNextCmd extends Command<String> {
+        @Override
+        protected String run(IPlaybackService iService) throws RemoteException {
+            return iService.getTitleNext();
+        }
+    }
+
+    private static class IsPlayingCmd extends Command<Boolean> {
+        @Override
+        protected Boolean run(IPlaybackService iService) throws RemoteException {
+            return iService.isPlaying();
+        }
+    }
+
+    private static class PauseCmd extends Command<Void> {
+        @Override
+        protected Void run(IPlaybackService iService) throws RemoteException {
+            iService.pause(); return null;
+        }
+    }
+
+    private static class PlayCmd extends Command<Void> {
+        @Override
+        protected Void run(IPlaybackService iService) throws RemoteException {
+            iService.play(); return null;
+        }
+    }
+
+    private static class HasMediasCmd extends Command<Boolean> {
+        @Override
+        protected Boolean run(IPlaybackService iService) throws RemoteException {
+            return iService.hasMedia();
+        }
+    }
+
+    private static class GetLengthCmd extends Command<Integer> {
+        @Override
+        protected Integer run(IPlaybackService iService) throws RemoteException {
+            return iService.getLength();
+        }
+    }
+
+    private static class GetTimeCmd extends Command<Integer> {
+        @Override
+        protected Integer run(IPlaybackService iService) throws RemoteException {
+            return iService.getTime();
+        }
+    }
+
+    private static class GetCoverCmd extends Command<Bitmap> {
+        @Override
+        protected Bitmap run(IPlaybackService iService) throws RemoteException {
+            return iService.getCover();
+        }
+    }
+
+    private static class GetCoverPrevCmd extends Command<Bitmap> {
+        @Override
+        protected Bitmap run(IPlaybackService iService) throws RemoteException {
+            return iService.getCoverPrev();
+        }
+    }
+
+    private static class GetCoverNextCmd extends Command<Bitmap> {
+        @Override
+        protected Bitmap run(IPlaybackService iService) throws RemoteException {
+            return iService.getCoverNext();
+        }
+    }
+
+    private static class NextCmd extends Command<Void> {
+        @Override
+        protected Void run(IPlaybackService iService) throws RemoteException {
+            iService.next(); return null;
+        }
+    }
+
+    private static class PreviousCmd extends Command<Void> {
+        @Override
+        protected Void run(IPlaybackService iService) throws RemoteException {
+            iService.previous(); return null;
+        }
+    }
+
+    private static class SetTimeCmd extends Command<Void> {
+        final long time;
+
+        private SetTimeCmd(long time) {
+            this.time = time;
+        }
+
+        @Override
+        protected Void run(IPlaybackService iService) throws RemoteException {
+            iService.setTime(time); return null;
+        }
+    }
+
+    private static class HasNextCmd extends Command<Boolean> {
+        @Override
+        protected Boolean run(IPlaybackService iService) throws RemoteException {
+            return iService.hasNext();
+        }
+    }
+
+    private static class HasPreviousCmd extends Command<Boolean> {
+        @Override
+        protected Boolean run(IPlaybackService iService) throws RemoteException {
+            return iService.hasPrevious();
+        }
+    }
+
+    private static class ShuffleCmd extends Command<Void> {
+        @Override
+        protected Void run(IPlaybackService iService) throws RemoteException {
+            iService.shuffle(); return null;
+        }
+    }
+
+    private static class IsShufflingCmd extends Command<Boolean> {
+        @Override
+        protected Boolean run(IPlaybackService iService) throws RemoteException {
+            return iService.isShuffling();
+        }
+    }
+
+    private static class SetRepeatTypeCmd extends Command<Void> {
+        final PlaybackService.RepeatType type;
+
+        private SetRepeatTypeCmd(PlaybackService.RepeatType type) {
+            this.type = type;
+        }
+
+        @Override
+        protected Void run(IPlaybackService iService) throws RemoteException {
+            iService.setRepeatType(type.ordinal()); return null;
+        }
+    }
+
+    private static class GetRepeatTypeCmd extends Command<PlaybackService.RepeatType> {
+        @Override
+        protected PlaybackService.RepeatType run(IPlaybackService iService) throws RemoteException {
+            return PlaybackService.RepeatType.values()[iService.getRepeatType()];
+        }
+    }
+
+    private static class DetectHeadsetCmd extends Command<Void> {
+        final boolean enable;
+
+        private DetectHeadsetCmd(boolean enable) {
+            this.enable = enable;
+        }
+
+        @Override
+        protected Void run(IPlaybackService iService) throws RemoteException {
+            iService.detectHeadset(enable); return null;
+        }
+    }
+
+    private static class GetRateCmd extends Command<Float> {
+        @Override
+        protected Float run(IPlaybackService iService) throws RemoteException {
+            return iService.getRate();
+        }
+    }
+
+    private static class HandleVout extends Command<Void> {
+        @Override
+        protected Void run(IPlaybackService iService) throws RemoteException {
+            iService.handleVout(); return null;
+        }
     }
 
     public void load(List<MediaWrapper> mediaList, int position) {
         load(mediaList, position, false);
     }
-
     public void load(List<MediaWrapper> mediaList, int position, boolean forceAudio) {
-        remoteProcedureCall(mIService, Void.class, (Void) null, "load",
-                new Class<?>[]{List.class, int.class, boolean.class},
-                new Object[]{mediaList, position, forceAudio});
+        new LoadCmd(mediaList, position, forceAudio).send(mIService);
+    }
+    public void load(MediaWrapper media, boolean forceAudio) {
+        ArrayList<MediaWrapper> arrayList = new ArrayList<>();
+        arrayList.add(media);
+        load(arrayList, 0, forceAudio);
+    }
+    public void load(MediaWrapper media) {
+        load(media, false);
+    }
+    public void loadLocation(String mediaPath) {
+        ArrayList < String > arrayList = new ArrayList<>();
+        arrayList.add(mediaPath);
+        loadLocations(arrayList, 0);
+    }
+    public void loadLocations(List<String> mediaPathList, int position) {
+        new LoadLocationsCmd(mediaPathList, position).send(mIService);
     }
-
     public void append(MediaWrapper media) {
-        ArrayList<MediaWrapper> arrayList = new ArrayList<MediaWrapper>();
+        ArrayList<MediaWrapper> arrayList = new ArrayList<>();
         arrayList.add(media);
         append(arrayList);
     }
-
     public void append(List<MediaWrapper> mediaList) {
-        remoteProcedureCall(mIService, Void.class, (Void) null, "append",
-                new Class<?>[]{List.class},
-                new Object[]{mediaList});
+        new AppendCmd(mediaList).send(mIService);
     }
-
     public void moveItem(int positionStart, int positionEnd) {
-        remoteProcedureCall(mIService, Void.class, (Void)null, "moveItem",
-                new Class<?>[] { int.class, int.class },
-                new Object[] { positionStart, positionEnd } );
+        new MoveItemCmd(positionStart, positionEnd).send(mIService);
     }
-
     public void remove(int position) {
-        remoteProcedureCall(mIService, Void.class, (Void)null, "remove",
-                new Class<?>[] { int.class },
-                new Object[] { position } );
+        new RemoveCmd(position).send(mIService);
     }
-
     public void removeLocation(String location) {
-        remoteProcedureCall(mIService, Void.class, (Void)null, "removeLocation",
-                new Class<?>[] { String.class },
-                new Object[] { location } );
+        new RemoveLocationCmd(location).send(mIService);
     }
-
-    @SuppressWarnings("unchecked")
     public List<MediaWrapper> getMedias() {
-        return remoteProcedureCall(mIService, List.class, null, "getMedias", null, null);
+        return new GetMediasCmd().send(mIService);
     }
-
-    @SuppressWarnings("unchecked")
     public List<String> getMediaLocations() {
-        List<String> def = new ArrayList<String>();
-        return remoteProcedureCall(mIService, List.class, def, "getMediaLocations", null, null);
+        return new GetMediaLocationsCmd().send(mIService);
     }
-
     public String getCurrentMediaLocation() {
-        return remoteProcedureCall(mIService, String.class, (String)null, "getCurrentMediaLocation", null, null);
+        return new GetCurrentMediaLocationCmd().send(mIService);
     }
-
     public MediaWrapper getCurrentMediaWrapper() {
-        return remoteProcedureCall(mIService, MediaWrapper.class, (MediaWrapper)null, "getCurrentMediaWrapper", null, null);
+        return new GetCurrentMediaWrapperCmd().send(mIService);
     }
-
     public void stop() {
-        remoteProcedureCall(mIService, Void.class, (Void)null, "stop", null, null);
+        new StopCmd().send(mIService);
     }
-
     public void showWithoutParse(int u) {
-        remoteProcedureCall(mIService, Void.class, (Void)null, "showWithoutParse",
-                new Class<?>[] { int.class },
-                new Object[] { u } );
+        new ShowWithoutParseCmd(u).send(mIService);
     }
-
     public void playIndex(int i) {
-        remoteProcedureCall(mIService, Void.class, (Void)null, "playIndex",
-                new Class<?>[] { int.class },
-                new Object[] { i } );
+        new PlayIndexCmd(i).send(mIService);
     }
-
     public String getAlbum() {
-        return remoteProcedureCall(mIService, String.class, (String)null, "getAlbum", null, null);
+        return new GetAlbumCmd().send(mIService);
     }
-
     public String getArtist() {
-        return remoteProcedureCall(mIService, String.class, (String)null, "getArtist", null, null);
+        return new GetArtistCmd().send(mIService);
     }
-
     public String getArtistPrev() {
-        return remoteProcedureCall(mIService, String.class, (String)null, "getArtistPrev", null, null);
+        return new GetArtistPrevCmd().send(mIService);
     }
-
     public String getArtistNext() {
-        return remoteProcedureCall(mIService, String.class, (String)null, "getArtistNext", null, null);
+        return new GetArtistNextCmd().send(mIService);
     }
-
     public String getTitle() {
-        return remoteProcedureCall(mIService, String.class, (String)null, "getTitle", null, null);
+        return new GetTitleCmd().send(mIService);
     }
-
     public String getTitlePrev() {
-        return remoteProcedureCall(mIService, String.class, (String)null, "getTitlePrev", null, null);
+        return new GetTitlePrevCmd().send(mIService);
     }
-
     public String getTitleNext() {
-        return remoteProcedureCall(mIService, String.class, (String)null, "getTitleNext", null, null);
+        return new GetTitleNextCmd().send(mIService);
     }
-
     public boolean isPlaying() {
-        return hasMedia() && remoteProcedureCall(mIService, boolean.class, false, "isPlaying", null, null);
+        return new IsPlayingCmd().send(mIService, false);
     }
-
     public void pause() {
-        remoteProcedureCall(mIService, Void.class, (Void)null, "pause", null, null);
+        new PauseCmd().send(mIService);
     }
-
     public void play() {
-        remoteProcedureCall(mIService, Void.class, (Void)null, "play", null, null);
+        new PlayCmd().send(mIService);
     }
-
     public boolean hasMedia() {
-        return remoteProcedureCall(mIService, boolean.class, false, "hasMedia", null, null);
+        return new HasMediasCmd().send(mIService, false);
     }
-
     public int getLength() {
-        return remoteProcedureCall(mIService, int.class, 0, "getLength", null, null);
+        return new GetLengthCmd().send(mIService, 0);
     }
-
     public int getTime() {
-        return remoteProcedureCall(mIService, int.class, 0, "getTime", null, null);
+        return new GetTimeCmd().send(mIService, 0);
     }
-
     public Bitmap getCover() {
-        return remoteProcedureCall(mIService, Bitmap.class, (Bitmap)null, "getCover", null, null);
+        return new GetCoverCmd().send(mIService);
     }
-
     public Bitmap getCoverPrev() {
-        return remoteProcedureCall(mIService, Bitmap.class, (Bitmap)null, "getCoverPrev", null, null);
+        return new GetCoverPrevCmd().send(mIService);
     }
-
     public Bitmap getCoverNext() {
-        return remoteProcedureCall(mIService, Bitmap.class, (Bitmap)null, "getCoverNext", null, null);
+        return new GetCoverNextCmd().send(mIService);
     }
-
     public void next() {
-        remoteProcedureCall(mIService, Void.class, (Void)null, "next", null, null);
+        new NextCmd().send(mIService);
     }
-
     public void previous() {
-        remoteProcedureCall(mIService, Void.class, (Void)null, "previous", null, null);
+        new PreviousCmd().send(mIService);
     }
-
     public void setTime(long time) {
-        remoteProcedureCall(mIService, Void.class, (Void)null, "setTime",
-                new Class<?>[] { long.class },
-                new Object[] { time } );
+        new SetTimeCmd(time).send(mIService);
     }
-
     public boolean hasNext() {
-        return remoteProcedureCall(mIService, boolean.class, false, "hasNext", null, null);
+        return new HasNextCmd().send(mIService, false);
     }
-
     public boolean hasPrevious() {
-        return remoteProcedureCall(mIService, boolean.class, false, "hasPrevious", null, null);
+        return new HasPreviousCmd().send(mIService, false);
     }
-
     public void shuffle() {
-        remoteProcedureCall(mIService, Void.class, (Void)null, "shuffle", null, null);
-    }
-
-    public void setRepeatType(PlaybackService.RepeatType t) {
-        remoteProcedureCall(mIService, Void.class, (Void)null, "setRepeatType",
-                new Class<?>[] { int.class },
-                new Object[] { t.ordinal() } );
+        new ShuffleCmd().send(mIService);
     }
-
     public boolean isShuffling() {
-        return remoteProcedureCall(mIService, boolean.class, false, "isShuffling", null, null);
+        return new IsShufflingCmd().send(mIService, false);
+    }
+    public void setRepeatType(PlaybackService.RepeatType t) {
+        new SetRepeatTypeCmd(t).send(mIService);
     }
-
     public PlaybackService.RepeatType getRepeatType() {
-        return PlaybackService.RepeatType.values()[
-            remoteProcedureCall(mIService, int.class, PlaybackService.RepeatType.None.ordinal(), "getRepeatType", null, null)
-        ];
+        return new GetRepeatTypeCmd().send(mIService, PlaybackService.RepeatType.None);
     }
-
     public void detectHeadset(boolean enable) {
-        remoteProcedureCall(mIService, Void.class, null, "detectHeadset",
-                new Class<?>[] { boolean.class },
-                new Object[] { enable } );
+        new DetectHeadsetCmd(enable).send(mIService);
     }
-
     public float getRate() {
-        return remoteProcedureCall(mIService, Float.class, (float) 1.0, "getRate", null, null);
+        return new GetRateCmd().send(mIService, 1.0f);
     }
-
     public void handleVout() {
-        remoteProcedureCall(mIService, Void.class, (Void)null, "handleVout", null, null);
+        new HandleVout().send(mIService);
+    }
+
+    public static final class Async {
+        public static void load(Context context, AsyncCallback<Void> asyncCb, MediaWrapper media, boolean forceAudio) {
+            ArrayList<MediaWrapper> arrayList = new ArrayList<>();
+            arrayList.add(media);
+            load(context, asyncCb, arrayList, 0, forceAudio);
+        }
+        public static void load(Context context, AsyncCallback<Void> asyncCb, MediaWrapper media) {
+            load(context, asyncCb, media, false);
+        }
+        public static void load(Context context, AsyncCallback<Void> asyncCb, List<MediaWrapper> mediaList, int position) {
+            load(context, asyncCb, mediaList, position, false);
+        }
+        public static void load(Context context, AsyncCallback<Void> asyncCb, List<MediaWrapper> mediaList, int position, boolean forceAudio) {
+            new LoadCmd(mediaList, position, forceAudio).sendAsync(context, asyncCb);
+        }
+        public static void loadLocation(Context context, AsyncCallback<Void> asyncCb, String mediaPath) {
+            ArrayList < String > arrayList = new ArrayList<>();
+            arrayList.add(mediaPath);
+            loadLocations(context, asyncCb, arrayList, 0);
+        }
+        public static void loadLocations(Context context, AsyncCallback<Void> asyncCb, List<String> mediaPathList, int position) {
+            new LoadLocationsCmd(mediaPathList, position).sendAsync(context, asyncCb);
+        }
+        public static void append(Context context, AsyncCallback<Void> asyncCb, MediaWrapper media) {
+            ArrayList<MediaWrapper> arrayList = new ArrayList<>();
+            arrayList.add(media);
+            append(context, asyncCb, arrayList);
+        }
+        public static void append(Context context, AsyncCallback<Void> asyncCb, List<MediaWrapper> mediaList) {
+            new AppendCmd(mediaList).sendAsync(context, asyncCb);
+        }
+        public static void moveItem(Context context, AsyncCallback<Void> asyncCb, int positionStart, int positionEnd) {
+            new MoveItemCmd(positionStart, positionEnd).sendAsync(context, asyncCb);
+        }
+        public static void remove(Context context, AsyncCallback<Void> asyncCb, int position) {
+            new RemoveCmd(position).sendAsync(context, asyncCb);
+        }
+        public static void removeLocation(Context context, AsyncCallback<Void> asyncCb, String location) {
+            new RemoveLocationCmd(location).sendAsync(context, asyncCb);
+        }
+        public static void getMedias(Context context, AsyncCallback<List<MediaWrapper>> asyncCb) {
+            new GetMediasCmd().sendAsync(context, asyncCb);
+        }
+        public static void getMediaLocations(Context context, AsyncCallback<List<String>> asyncCb) {
+            new GetMediaLocationsCmd().sendAsync(context, asyncCb);
+        }
+        public static void getCurrentMediaLocation(Context context, AsyncCallback<String> asyncCb) {
+            new GetCurrentMediaLocationCmd().sendAsync(context, asyncCb);
+        }
+        public static void getCurrentMediaWrapper(Context context, AsyncCallback<MediaWrapper> asyncCb) {
+            new GetCurrentMediaWrapperCmd().sendAsync(context, asyncCb);
+        }
+        public static void stop(Context context, AsyncCallback<Void> asyncCb) {
+            new StopCmd().sendAsync(context, asyncCb);
+        }
+        public static void showWithoutParse(Context context, AsyncCallback<Void> asyncCb, int u) {
+            new ShowWithoutParseCmd(u).sendAsync(context, asyncCb);
+        }
+        public static void playIndex(Context context, AsyncCallback<Void> asyncCb, int i) {
+            new PlayIndexCmd(i).sendAsync(context, asyncCb);
+        }
+        public static void getAlbum(Context context, AsyncCallback<String> asyncCb) {
+            new GetAlbumCmd().sendAsync(context, asyncCb);
+        }
+        public static void getArtist(Context context, AsyncCallback<String> asyncCb) {
+            new GetArtistCmd().sendAsync(context, asyncCb);
+        }
+        public static void getArtistPrev(Context context, AsyncCallback<String> asyncCb) {
+            new GetArtistPrevCmd().sendAsync(context, asyncCb);
+        }
+        public static void getArtistNext(Context context, AsyncCallback<String> asyncCb) {
+            new GetArtistNextCmd().sendAsync(context, asyncCb);
+        }
+        public static void getTitle(Context context, AsyncCallback<String> asyncCb) {
+            new GetTitleCmd().sendAsync(context, asyncCb);
+        }
+        public static void getTitlePrev(Context context, AsyncCallback<String> asyncCb) {
+            new GetTitlePrevCmd().sendAsync(context, asyncCb);
+        }
+        public static void getTitleNext(Context context, AsyncCallback<String> asyncCb) {
+            new GetTitleNextCmd().sendAsync(context, asyncCb);
+        }
+        public static void isPlaying(Context context, AsyncCallback<Boolean> asyncCb) {
+            new IsPlayingCmd().sendAsync(context, asyncCb);
+        }
+        public static void pause(Context context, AsyncCallback<Void> asyncCb) {
+            new PauseCmd().sendAsync(context, asyncCb);
+        }
+        public static void play(Context context, AsyncCallback<Void> asyncCb) {
+            new PlayCmd().sendAsync(context, asyncCb);
+        }
+        public static void hasMedia(Context context, AsyncCallback<Boolean> asyncCb) {
+            new HasMediasCmd().sendAsync(context, asyncCb);
+        }
+        public static void getLength(Context context, AsyncCallback<Integer> asyncCb) {
+            new GetLengthCmd().sendAsync(context, asyncCb);
+        }
+        public static void getTime(Context context, AsyncCallback<Integer> asyncCb) {
+            new GetTimeCmd().sendAsync(context, asyncCb);
+        }
+        public static void getCover(Context context, AsyncCallback<Bitmap> asyncCb) {
+            new GetCoverCmd().sendAsync(context, asyncCb);
+        }
+        public static void getCoverPrev(Context context, AsyncCallback<Bitmap> asyncCb) {
+            new GetCoverPrevCmd().sendAsync(context, asyncCb);
+        }
+        public static void getCoverNext(Context context, AsyncCallback<Bitmap> asyncCb) {
+            new GetCoverNextCmd().sendAsync(context, asyncCb);
+        }
+        public static void next(Context context, AsyncCallback<Void> asyncCb) {
+            new NextCmd().sendAsync(context, asyncCb);
+        }
+        public static void previous(Context context, AsyncCallback<Void> asyncCb) {
+            new PreviousCmd().sendAsync(context, asyncCb);
+        }
+        public static void setTime(Context context, AsyncCallback<Void> asyncCb, long time) {
+            new SetTimeCmd(time).sendAsync(context, asyncCb);
+        }
+        public static void hasNext(Context context, AsyncCallback<Boolean> asyncCb) {
+            new HasNextCmd().sendAsync(context, asyncCb);
+        }
+        public static void hasPrevious(Context context, AsyncCallback<Boolean> asyncCb) {
+            new HasPreviousCmd().sendAsync(context, asyncCb);
+        }
+        public static void shuffle(Context context, AsyncCallback<Void> asyncCb) {
+            new ShuffleCmd().sendAsync(context, asyncCb);
+        }
+        public static void isShuffling(Context context, AsyncCallback<Boolean> asyncCb) {
+            new IsShufflingCmd().sendAsync(context, asyncCb);
+        }
+        public static void setRepeatType(Context context, AsyncCallback<Void> asyncCb, PlaybackService.RepeatType t) {
+            new SetRepeatTypeCmd(t).sendAsync(context, asyncCb);
+        }
+        public static void getRepeatType(Context context, AsyncCallback<PlaybackService.RepeatType> asyncCb) {
+            new GetRepeatTypeCmd().sendAsync(context, asyncCb);
+        }
+        public static void detectHeadset(Context context, AsyncCallback<Void> asyncCb, boolean enable) {
+            new DetectHeadsetCmd(enable).sendAsync(context, asyncCb);
+        }
+        public static void getRate(Context context, AsyncCallback<Float> asyncCb) {
+            new GetRateCmd().sendAsync(context, asyncCb);
+        }
+        public static void handleVout(Context context, AsyncCallback<Void> asyncCb) {
+            new HandleVout().sendAsync(context, asyncCb);
+        }
     }
 }
diff --git a/vlc-android/src/org/videolan/vlc/gui/AudioPlayerContainerActivity.java b/vlc-android/src/org/videolan/vlc/gui/AudioPlayerContainerActivity.java
index d3fb65b..16ca9b4 100644
--- a/vlc-android/src/org/videolan/vlc/gui/AudioPlayerContainerActivity.java
+++ b/vlc-android/src/org/videolan/vlc/gui/AudioPlayerContainerActivity.java
@@ -23,6 +23,7 @@
 
 package org.videolan.vlc.gui;
 
+import android.app.Activity;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -32,6 +33,7 @@ import android.os.Bundle;
 import android.preference.PreferenceManager;
 import android.support.v7.app.ActionBar;
 import android.support.v7.app.AppCompatActivity;
+import android.support.v4.app.Fragment;
 import android.support.v7.widget.Toolbar;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -39,6 +41,7 @@ import android.view.ViewGroup;
 import android.widget.TextView;
 
 import org.videolan.vlc.BuildConfig;
+import org.videolan.vlc.MediaWrapper;
 import org.videolan.vlc.PlaybackServiceClient;
 import org.videolan.vlc.R;
 import org.videolan.vlc.gui.audio.AudioPlayer;
@@ -46,18 +49,18 @@ import org.videolan.vlc.util.Util;
 import org.videolan.vlc.widget.HackyDrawerLayout;
 import com.android.widget.SlidingPaneLayout;
 
-public class AudioPlayerContainerActivity extends AppCompatActivity {
+public class AudioPlayerContainerActivity extends AppCompatActivity  {
 
     public static final String ACTION_SHOW_PLAYER = "org.videolan.vlc.gui.ShowPlayer";
 
     protected ActionBar mActionBar;
     protected Toolbar mToolbar;
     protected AudioPlayer mAudioPlayer;
-    protected PlaybackServiceClient mAudioController;
     protected SlidingPaneLayout mSlidingPane;
     protected View mAudioPlayerFilling;
     protected SharedPreferences mSettings;
     protected ViewGroup mRootContainer;
+    private PlaybackServiceClient mClient;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -65,6 +68,11 @@ public class AudioPlayerContainerActivity extends AppCompatActivity {
         mSettings = PreferenceManager.getDefaultSharedPreferences(this);
         /* Theme must be applied before super.onCreate */
         applyTheme();
+
+        /* Set up the audio player */
+        mAudioPlayer = new AudioPlayer();
+        mClient = new PlaybackServiceClient(this, mAudioPlayer);
+
         super.onCreate(savedInstanceState);
     }
 
@@ -80,10 +88,7 @@ public class AudioPlayerContainerActivity extends AppCompatActivity {
         mSlidingPane.setPanelSlideListener(mPanelSlideListener);
         mAudioPlayerFilling = findViewById(R.id.audio_player_filling);
 
-        /* Set up the audio player */
-        mAudioPlayer = new AudioPlayer();
         mAudioPlayer.setUserVisibleHint(false);
-        mAudioController = PlaybackServiceClient.getInstance();
 
         getSupportFragmentManager().beginTransaction()
                 .replace(R.id.audio_player, mAudioPlayer)
@@ -98,6 +103,7 @@ public class AudioPlayerContainerActivity extends AppCompatActivity {
         IntentFilter filter = new IntentFilter();
         filter.addAction(ACTION_SHOW_PLAYER);
         registerReceiver(messageReceiver, filter);
+        mClient.connect();
     }
 
     @Override
@@ -106,20 +112,7 @@ public class AudioPlayerContainerActivity extends AppCompatActivity {
         try {
             unregisterReceiver(messageReceiver);
         } catch (IllegalArgumentException e) {}
-    }
-
-    @Override
-    protected void onResume() {
-        super.onResume();
-        mAudioController.addCallback(mAudioPlayer);
-        PlaybackServiceClient.getInstance().bindAudioService(this);
-    }
-
-    @Override
-    protected void onPause() {
-        super.onPause();
-        mAudioController.removeCallback(mAudioPlayer);
-        PlaybackServiceClient.getInstance().unbindAudioService(this);
+        mClient.disconnect();
     }
 
     private void applyTheme() {
@@ -278,4 +271,16 @@ public class AudioPlayerContainerActivity extends AppCompatActivity {
 
     protected void onPanelOpenedUiSet() {}
 
+    public PlaybackServiceClient getPlaybackClient() {
+        return mClient;
+    }
+
+    public static PlaybackServiceClient getPlaybackClient(Fragment fragment) {
+        final Activity activity = fragment.getActivity();
+        if (activity == null)
+            return null;
+        if (!(activity instanceof AudioPlayerContainerActivity))
+            throw new IllegalArgumentException("Fragment must be inside AudioPlayerContainerActivity");
+        return ((AudioPlayerContainerActivity) activity).getPlaybackClient();
+    }
 }
diff --git a/vlc-android/src/org/videolan/vlc/gui/HistoryAdapter.java b/vlc-android/src/org/videolan/vlc/gui/HistoryAdapter.java
index 0f10d9a..0dd6fcb 100644
--- a/vlc-android/src/org/videolan/vlc/gui/HistoryAdapter.java
+++ b/vlc-android/src/org/videolan/vlc/gui/HistoryAdapter.java
@@ -43,21 +43,13 @@ public class HistoryAdapter extends BaseAdapter implements PlaybackServiceClient
     public final static String TAG = "VLC/HistoryAdapter";
 
     private LayoutInflater mInflater;
-    private final PlaybackServiceClient mAudioController;
     private final ArrayList<MediaWrapper> mMediaList;
 
     public HistoryAdapter(Context context) {
         mInflater = LayoutInflater.from(context);
 
-        mAudioController = PlaybackServiceClient.getInstance();
 
         mMediaList = new ArrayList<MediaWrapper>();
-
-        mAudioController.addCallback(this);
-    }
-
-    public void release () {
-        mAudioController.removeCallback(this);
     }
 
     @Override
@@ -112,7 +104,15 @@ public class HistoryAdapter extends BaseAdapter implements PlaybackServiceClient
     }
 
     public void remove(int position) {
-        mAudioController.remove(position);
+        PlaybackServiceClient.Async.remove(VLCApplication.getAppContext(), null, position);
+    }
+
+    @Override
+    public void onConnected() {
+    }
+
+    @Override
+    public void onDisconnected() {
     }
 
     @Override
diff --git a/vlc-android/src/org/videolan/vlc/gui/HistoryFragment.java b/vlc-android/src/org/videolan/vlc/gui/HistoryFragment.java
index ae8bd2e..ba7ced7 100644
--- a/vlc-android/src/org/videolan/vlc/gui/HistoryFragment.java
+++ b/vlc-android/src/org/videolan/vlc/gui/HistoryFragment.java
@@ -107,12 +107,6 @@ public class HistoryFragment extends MediaBrowserFragment implements IRefreshabl
     }
 
     @Override
-    public void onDestroy() {
-        mHistoryAdapter.release();
-        super.onDestroy();
-    }
-
-    @Override
     public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
         MenuInflater menuInflater = getActivity().getMenuInflater();
         menuInflater.inflate(R.menu.history_view, menu);
@@ -124,9 +118,7 @@ public class HistoryFragment extends MediaBrowserFragment implements IRefreshabl
     }
 
     private void playListIndex(int position) {
-        PlaybackServiceClient audioController = PlaybackServiceClient.getInstance();
-
-        audioController.playIndex(position);
+        PlaybackServiceClient.Async.playIndex(getActivity(), null, position);
     }
 
     @Override
diff --git a/vlc-android/src/org/videolan/vlc/gui/MainActivity.java b/vlc-android/src/org/videolan/vlc/gui/MainActivity.java
index 8914c10..7b1c5ad 100644
--- a/vlc-android/src/org/videolan/vlc/gui/MainActivity.java
+++ b/vlc-android/src/org/videolan/vlc/gui/MainActivity.java
@@ -202,7 +202,6 @@ public class MainActivity extends AudioPlayerContainerActivity implements OnItem
         reloadPreferences();
     }
 
-
     @Override
     protected void onPostCreate(Bundle savedInstanceState) {
         super.onPostCreate(savedInstanceState);
diff --git a/vlc-android/src/org/videolan/vlc/gui/PreferencesActivity.java b/vlc-android/src/org/videolan/vlc/gui/PreferencesActivity.java
index 184650c..618b773 100644
--- a/vlc-android/src/org/videolan/vlc/gui/PreferencesActivity.java
+++ b/vlc-android/src/org/videolan/vlc/gui/PreferencesActivity.java
@@ -21,7 +21,6 @@
 package org.videolan.vlc.gui;
 
 import android.app.Dialog;
-import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.SharedPreferences;
@@ -83,6 +82,7 @@ public class PreferencesActivity extends PreferenceActivity implements OnSharedP
         applyTheme();
 
         super.onCreate(savedInstanceState);
+
         addPreferencesFromResource(R.xml.preferences);
 
         if (!AndroidDevices.hasTsp()){
@@ -124,7 +124,7 @@ public class PreferencesActivity extends PreferenceActivity implements OnSharedP
                 new OnPreferenceClickListener() {
                     @Override
                     public boolean onPreferenceClick(Preference preference) {
-                        PlaybackServiceClient.getInstance().detectHeadset(checkboxHS.isChecked());
+                        PlaybackServiceClient.Async.detectHeadset(PreferencesActivity.this, null, checkboxHS.isChecked());
                         return true;
                     }
                 });
@@ -135,7 +135,7 @@ public class PreferencesActivity extends PreferenceActivity implements OnSharedP
                 new OnPreferenceClickListener() {
                     @Override
                     public boolean onPreferenceClick(Preference preference) {
-                        restartService(preference.getContext());
+                        PlaybackServiceClient.restartService(PreferencesActivity.this);
                         return true;
                     }
                 });
@@ -283,6 +283,11 @@ public class PreferencesActivity extends PreferenceActivity implements OnSharedP
         sharedPrefs.registerOnSharedPreferenceChangeListener(this);
     }
 
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+    }
+
     private void applyTheme() {
         SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(this);
         boolean enableBlackTheme = pref.getBoolean("enable_black_theme", false);
@@ -347,7 +352,7 @@ public class PreferencesActivity extends PreferenceActivity implements OnSharedP
                 || key.equalsIgnoreCase("network_caching")
                 || key.equalsIgnoreCase("dev_hardware_decoder")) {
             VLCInstance.restart(this, sharedPreferences);
-            restartService(this);
+            PlaybackServiceClient.restartService(PreferencesActivity.this);
         }
     }
 
@@ -370,26 +375,4 @@ public class PreferencesActivity extends PreferenceActivity implements OnSharedP
         } catch(Exception e){}
         return false;
     }
-
-    @Override
-    protected void onResume() {
-        super.onResume();
-        PlaybackServiceClient.getInstance().bindAudioService(this);
-    }
-
-    @Override
-    protected void onPause() {
-        super.onPause();
-        PlaybackServiceClient.getInstance().unbindAudioService(this);
-    }
-
-    private void restartService(Context context) {
-        Intent service = new Intent(context, PlaybackService.class);
-
-        PlaybackServiceClient.getInstance().unbindAudioService(PreferencesActivity.this);
-        context.stopService(service);
-
-        context.startService(service);
-        PlaybackServiceClient.getInstance().bindAudioService(PreferencesActivity.this);
-    }
 }
diff --git a/vlc-android/src/org/videolan/vlc/gui/audio/AudioAlbumFragment.java b/vlc-android/src/org/videolan/vlc/gui/audio/AudioAlbumFragment.java
index d9e50e3..8e1dfc3 100644
--- a/vlc-android/src/org/videolan/vlc/gui/audio/AudioAlbumFragment.java
+++ b/vlc-android/src/org/videolan/vlc/gui/audio/AudioAlbumFragment.java
@@ -48,6 +48,7 @@ import org.videolan.libvlc.util.AndroidUtil;
 import org.videolan.vlc.MediaWrapper;
 import org.videolan.vlc.PlaybackServiceClient;
 import org.videolan.vlc.R;
+import org.videolan.vlc.gui.AudioPlayerContainerActivity;
 import org.videolan.vlc.util.AndroidDevices;
 
 import java.util.ArrayList;
@@ -56,25 +57,29 @@ public class AudioAlbumFragment extends Fragment implements AdapterView.OnItemCl
 
     public final static String TAG = "VLC/AudioAlbumFragment";
 
-    PlaybackServiceClient mAudioController;
-
     private AlbumAdapter mAdapter;
     private ArrayList<MediaWrapper> mMediaList;
     private String mTitle;
+    private PlaybackServiceClient mClient;
 
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         mAdapter = new AlbumAdapter(getActivity(), mMediaList);
+        mClient = AudioPlayerContainerActivity.getPlaybackClient(this);
 
         mAdapter.setContextPopupMenuListener(mContextPopupMenuListener);
 
-        mAudioController = PlaybackServiceClient.getInstance();
         if (savedInstanceState != null)
             setMediaList(savedInstanceState.<MediaWrapper>getParcelableArrayList("list"), savedInstanceState.getString("title"));
     }
 
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+    }
+
     public void setMediaList(ArrayList<MediaWrapper> mediaList, String title) {
         this.mMediaList = mediaList;
         mTitle = title;
@@ -123,7 +128,8 @@ public class AudioAlbumFragment extends Fragment implements AdapterView.OnItemCl
 
     @Override
     public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
-        mAudioController.load(mMediaList, position);
+        if (mClient.isConnected())
+            mClient.load(mMediaList, position);
     }
 
     AlbumAdapter.ContextPopupMenuListener mContextPopupMenuListener
@@ -198,7 +204,8 @@ public class AudioAlbumFragment extends Fragment implements AdapterView.OnItemCl
         final int id = v.getId();
         switch (id){
             case R.id.album_play:
-                mAudioController.load(mMediaList, 0);
+                if (mClient.isConnected())
+                    mClient.load(mMediaList, 0);
                 break;
             default:
                 break;
diff --git a/vlc-android/src/org/videolan/vlc/gui/audio/AudioAlbumsSongsFragment.java b/vlc-android/src/org/videolan/vlc/gui/audio/AudioAlbumsSongsFragment.java
index a54e14c..f23c144 100644
--- a/vlc-android/src/org/videolan/vlc/gui/audio/AudioAlbumsSongsFragment.java
+++ b/vlc-android/src/org/videolan/vlc/gui/audio/AudioAlbumsSongsFragment.java
@@ -55,6 +55,7 @@ import org.videolan.vlc.MediaWrapper;
 import org.videolan.vlc.PlaybackServiceClient;
 import org.videolan.vlc.R;
 import org.videolan.vlc.VLCApplication;
+import org.videolan.vlc.gui.AudioPlayerContainerActivity;
 import org.videolan.vlc.gui.SecondaryActivity;
 import org.videolan.vlc.util.AndroidDevices;
 import org.videolan.vlc.util.Util;
@@ -74,8 +75,8 @@ public class AudioAlbumsSongsFragment extends Fragment implements SwipeRefreshLa
     private static final int DELETE_MEDIA = 0;
     private static final int DELETE_DURATION = 3000;
 
-    PlaybackServiceClient mAudioController;
     private MediaLibrary mMediaLibrary;
+    private PlaybackServiceClient mClient;
 
     private SwipeRefreshLayout mSwipeRefreshLayout;
     private ViewPager mViewPager;
@@ -100,13 +101,13 @@ public class AudioAlbumsSongsFragment extends Fragment implements SwipeRefreshLa
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+        mClient = AudioPlayerContainerActivity.getPlaybackClient(this);
         mAlbumsAdapter = new AudioBrowserListAdapter(getActivity(), AudioBrowserListAdapter.ITEM_WITH_COVER);
         mSongsAdapter = new AudioBrowserListAdapter(getActivity(), AudioBrowserListAdapter.ITEM_WITH_COVER);
 
         mAlbumsAdapter.setContextPopupMenuListener(mContextPopupMenuListener);
         mSongsAdapter.setContextPopupMenuListener(mContextPopupMenuListener);
 
-        mAudioController = PlaybackServiceClient.getInstance();
         mMediaLibrary = MediaLibrary.getInstance();
         if (savedInstanceState != null)
             setMediaList(savedInstanceState.<MediaWrapper>getParcelableArrayList("list"), savedInstanceState.getString("title"));
@@ -129,7 +130,7 @@ public class AudioAlbumsSongsFragment extends Fragment implements SwipeRefreshLa
         List<View> lists = Arrays.asList((View)albumsList, songsList);
         String[] titles = new String[] {getString(R.string.albums), getString(R.string.songs)};
         mViewPager = (ViewPager) v.findViewById(R.id.pager);
-        mViewPager.setOffscreenPageLimit(MODE_TOTAL-1);
+        mViewPager.setOffscreenPageLimit(MODE_TOTAL - 1);
         mViewPager.setAdapter(new AudioPagerAdapter(lists, titles));
 
         mViewPager.setOnTouchListener(mSwipeFilter);
@@ -182,11 +183,6 @@ public class AudioAlbumsSongsFragment extends Fragment implements SwipeRefreshLa
     }
 
     @Override
-    public void onDestroy() {
-        super.onDestroy();
-    }
-
-    @Override
     public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
         int position = 0;
         if (menuInfo instanceof AdapterContextMenuInfo)
@@ -261,10 +257,12 @@ public class AudioAlbumsSongsFragment extends Fragment implements SwipeRefreshLa
             }
         }
 
-        if (append)
-            mAudioController.append(medias);
-        else
-            mAudioController.load(medias, startPosition);
+        if (mClient.isConnected()) {
+            if (append)
+                mClient.append(medias);
+            else
+                mClient.load(medias, startPosition);
+        }
 
         return super.onContextItemSelected(item);
     }
@@ -315,8 +313,10 @@ public class AudioAlbumsSongsFragment extends Fragment implements SwipeRefreshLa
     OnItemClickListener songsListener = new OnItemClickListener() {
         @Override
         public void onItemClick(AdapterView<?> av, View v, int p, long id) {
-            List<MediaWrapper> media = mSongsAdapter.getItem(p).mMediaList;
-            mAudioController.load(media, 0);
+            if (mClient.isConnected()) {
+                final List<MediaWrapper> media = mSongsAdapter.getItem(p).mMediaList;
+                mClient.load(media, 0);
+            }
         }
     };
 
@@ -396,7 +396,8 @@ public class AudioAlbumsSongsFragment extends Fragment implements SwipeRefreshLa
                     fragment.mMediaLibrary.getMediaItems().remove(media);
                     fragment.mSongsAdapter.removeMedia(media);
                     fragment.mAlbumsAdapter.removeMedia(media);
-                    fragment.mAudioController.removeLocation(media.getLocation());
+                    if (fragment.mClient.isConnected())
+                        fragment.mClient.removeLocation(media.getLocation());
                     fragment.mMediaLibrary.getMediaItems().remove(media);
                     new Thread(new Runnable() {
                         public void run() {
diff --git a/vlc-android/src/org/videolan/vlc/gui/audio/AudioBrowserFragment.java b/vlc-android/src/org/videolan/vlc/gui/audio/AudioBrowserFragment.java
index cfdbe9c..8fa4421 100644
--- a/vlc-android/src/org/videolan/vlc/gui/audio/AudioBrowserFragment.java
+++ b/vlc-android/src/org/videolan/vlc/gui/audio/AudioBrowserFragment.java
@@ -59,6 +59,7 @@ import org.videolan.vlc.MediaWrapper;
 import org.videolan.vlc.PlaybackServiceClient;
 import org.videolan.vlc.R;
 import org.videolan.vlc.VLCApplication;
+import org.videolan.vlc.gui.AudioPlayerContainerActivity;
 import org.videolan.vlc.gui.MainActivity;
 import org.videolan.vlc.gui.SecondaryActivity;
 import org.videolan.vlc.gui.browser.MediaBrowserFragment;
@@ -82,7 +83,6 @@ import java.util.concurrent.Executors;
 public class AudioBrowserFragment extends MediaBrowserFragment implements SwipeRefreshLayout.OnRefreshListener, SlidingTabLayout.OnTabChangedListener, MediaBrowser.EventListener, IBrowser {
     public final static String TAG = "VLC/AudioBrowserFragment";
 
-    private PlaybackServiceClient mAudioController;
     private MediaLibrary mMediaLibrary;
     private MediaBrowser mMediaBrowser;
     private MainActivity mMainActivity;
@@ -113,6 +113,7 @@ public class AudioBrowserFragment extends MediaBrowserFragment implements SwipeR
 
     public final static int MSG_LOADING = 0;
     private volatile boolean mDisplaying = false;
+    private PlaybackServiceClient mClient;
 
     /* All subclasses of Fragment must include a public empty constructor. */
     public AudioBrowserFragment() { }
@@ -121,9 +122,8 @@ public class AudioBrowserFragment extends MediaBrowserFragment implements SwipeR
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
-        mAudioController = PlaybackServiceClient.getInstance();
-
         mMediaLibrary = MediaLibrary.getInstance();
+        mClient = AudioPlayerContainerActivity.getPlaybackClient(this);
 
         mSongsAdapter = new AudioBrowserListAdapter(getActivity(), AudioBrowserListAdapter.ITEM_WITH_COVER);
         mArtistsAdapter = new AudioBrowserListAdapter(getActivity(), AudioBrowserListAdapter.ITEM_WITH_COVER);
@@ -318,7 +318,8 @@ public class AudioBrowserFragment extends MediaBrowserFragment implements SwipeR
     OnItemClickListener songListener = new OnItemClickListener() {
         @Override
         public void onItemClick(AdapterView<?> av, View v, int p, long id) {
-            mAudioController.load(mSongsAdapter.getMedias(p), 0);
+            if (mClient.isConnected())
+                mClient.load(mSongsAdapter.getMedias(p), 0);
         }
     };
 
@@ -368,10 +369,12 @@ public class AudioBrowserFragment extends MediaBrowserFragment implements SwipeR
 
     private void loadPlaylist(int position) {
         ArrayList<MediaWrapper> mediaList = mPlaylistAdapter.getItem(position).mMediaList;
+        if (!mClient.isConnected())
+            return;
         if (mediaList.size() == 1 && mediaList.get(0).getType() == MediaWrapper.TYPE_PLAYLIST) {
-            mAudioController.load(mediaList.get(0));
+            mClient.load(mediaList.get(0));
         } else {
-            mAudioController.load(mPlaylistAdapter.getMedias(position), 0);
+            mClient.load(mPlaylistAdapter.getMedias(position), 0);
         }
     }
 
@@ -490,11 +493,14 @@ public class AudioBrowserFragment extends MediaBrowserFragment implements SwipeR
             medias = adapter.getMedias(position);
         }
 
-        if (append)
-            mAudioController.append(medias);
-        else
-            mAudioController.load(medias, startPosition);
-        return true;
+        if (mClient.isConnected()) {
+            if (append)
+                mClient.append(medias);
+            else
+                mClient.load(medias, startPosition);
+            return true;
+        } else
+            return false;
     }
 
     View.OnClickListener mCancelDeletePlayListListener = new View.OnClickListener() {
@@ -516,8 +522,10 @@ public class AudioBrowserFragment extends MediaBrowserFragment implements SwipeR
         if (mSongsAdapter.getCount() > 0) {
             Random rand = new Random();
             int randomSong = rand.nextInt(mSongsAdapter.getCount());
-            mAudioController.load(medias, randomSong);
-            mAudioController.shuffle();
+            if (mClient.isConnected()) {
+                mClient.load(medias, randomSong);
+                mClient.shuffle();
+            }
         }
     }
 
@@ -617,7 +625,8 @@ public class AudioBrowserFragment extends MediaBrowserFragment implements SwipeR
 
     @Override
     public void onBrowseEnd() {
-        mAudioController.append(mTracksToAppend);
+        if (mClient.isConnected())
+            mClient.append(mTracksToAppend);
     }
 
     @Override
@@ -687,8 +696,12 @@ public class AudioBrowserFragment extends MediaBrowserFragment implements SwipeR
         }
 
         private void refresh(AudioBrowserFragment fragment, String path) {
-            if (fragment.mAudioController.getMediaLocations().contains(path))
-                fragment.mAudioController.removeLocation(path);
+            if (!fragment.mClient.isConnected())
+                return;
+
+            final List<String> mediaLocations = fragment.mClient.getMediaLocations();
+            if (mediaLocations != null && mediaLocations.contains(path))
+                fragment.mClient.removeLocation(path);
             fragment.updateLists();
         }
     }
diff --git a/vlc-android/src/org/videolan/vlc/gui/audio/AudioPlayer.java b/vlc-android/src/org/videolan/vlc/gui/audio/AudioPlayer.java
index 49a844c..25d0b05 100644
--- a/vlc-android/src/org/videolan/vlc/gui/audio/AudioPlayer.java
+++ b/vlc-android/src/org/videolan/vlc/gui/audio/AudioPlayer.java
@@ -90,7 +90,7 @@ public class AudioPlayer extends Fragment implements PlaybackServiceClient.Callb
 
     ViewSwitcher mSwitcher;
 
-    private PlaybackServiceClient mAudioController;
+    private PlaybackServiceClient mClient;
     private boolean mShowRemainingTime = false;
     private boolean mPreviewingSeek = false;
 
@@ -111,12 +111,16 @@ public class AudioPlayer extends Fragment implements PlaybackServiceClient.Callb
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
-        mAudioController = PlaybackServiceClient.getInstance();
-
         mSongsListAdapter = new AudioPlaylistAdapter(getActivity());
     }
 
     @Override
+    public void onStart() {
+        super.onStart();
+        mClient = AudioPlayerContainerActivity.getPlaybackClient(this);
+    }
+
+    @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
         View v = inflater.inflate(R.layout.audio_player, container, false);
 
@@ -228,7 +232,8 @@ public class AudioPlayer extends Fragment implements PlaybackServiceClient.Callb
         mSongsList.setOnItemClickListener(new OnItemClickListener() {
             @Override
             public void onItemClick(AdapterView<?> av, View v, int p, long id) {
-                mAudioController.playIndex(p);
+                if (mClient.isConnected())
+                    mClient.playIndex(p);
             }
         });
         mSongsList.setOnItemLongClickListener(new OnItemLongClickListener() {
@@ -242,13 +247,15 @@ public class AudioPlayer extends Fragment implements PlaybackServiceClient.Callb
         mSongsList.setOnItemDraggedListener(new AudioPlaylistView.OnItemDraggedListener() {
             @Override
             public void onItemDragged(int positionStart, int positionEnd) {
-                mAudioController.moveItem(positionStart, positionEnd);
+                if (mClient.isConnected())
+                    mClient.moveItem(positionStart, positionEnd);
             }
         });
         mSongsList.setOnItemRemovedListener(new AudioPlaylistView.OnItemRemovedListener() {
             @Override
             public void onItemRemoved(int position) {
-                mAudioController.remove(position);
+                if (mClient.isConnected())
+                    mClient.remove(position);
                 update();
             }
         });
@@ -291,7 +298,8 @@ public class AudioPlayer extends Fragment implements PlaybackServiceClient.Callb
 
             if(id == R.id.audio_player_mini_remove) {
                 Log.d(TAG, "Context menu removing " + info.position);
-                mAudioController.remove(info.position);
+                if (mClient.isConnected())
+                    mClient.remove(info.position);
                 return true;
             }
             return super.onContextItemSelected(item);
@@ -311,16 +319,23 @@ public class AudioPlayer extends Fragment implements PlaybackServiceClient.Callb
     }
 
     @Override
-    public synchronized void update() {
+    public void onConnected() {
+    }
+
+    @Override
+    public void onDisconnected() {
+    }
 
-        if (mAudioController == null)
+    @Override
+    public void update() {
+        if (!mClient.isConnected())
             return;
 
-        if (mAudioController.hasMedia()) {
+        if (mClient.hasMedia()) {
             SharedPreferences mSettings= PreferenceManager.getDefaultSharedPreferences(getActivity());
             if (mSettings.getBoolean(PreferencesActivity.VIDEO_RESTORE, false)){
                 Util.commitPreferences(mSettings.edit().putBoolean(PreferencesActivity.VIDEO_RESTORE, false));
-                mAudioController.handleVout();
+                mClient.handleVout();
                 return;
             }
             show();
@@ -329,12 +344,12 @@ public class AudioPlayer extends Fragment implements PlaybackServiceClient.Callb
             return;
         }
 
-        mHeaderMediaSwitcher.updateMedia();
-        mCoverMediaSwitcher.updateMedia();
+        mHeaderMediaSwitcher.updateMedia(mClient);
+        mCoverMediaSwitcher.updateMedia(mClient);
 
         FragmentActivity act = getActivity();
 
-        if (mAudioController.isPlaying()) {
+        if (mClient.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));
@@ -345,14 +360,14 @@ public class AudioPlayer extends Fragment implements PlaybackServiceClient.Callb
             mHeaderPlayPause.setImageResource(Util.getResourceFromAttribute(act, R.attr.ic_play));
             mHeaderPlayPause.setContentDescription(getString(R.string.play));
         }
-        if (mAudioController.isShuffling()) {
+        if (mClient.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(mAudioController.getRepeatType()) {
+        switch(mClient.getRepeatType()) {
         case None:
             mRepeat.setImageResource(Util.getResourceFromAttribute(act, R.attr.ic_repeat));
             mRepeat.setContentDescription(getResources().getString(R.string.repeat));
@@ -368,12 +383,13 @@ public class AudioPlayer extends Fragment implements PlaybackServiceClient.Callb
             break;
         }
 
-        mShuffle.setVisibility(mAudioController.getMediaLocations().size() > 2 ? View.VISIBLE : View.INVISIBLE);
-        if (mAudioController.hasNext())
+        final List<String> mediaLocations = mClient.getMediaLocations();
+        mShuffle.setVisibility(mediaLocations != null && mediaLocations.size() > 2 ? View.VISIBLE : View.INVISIBLE);
+        if (mClient.hasNext())
             mNext.setVisibility(ImageButton.VISIBLE);
         else
             mNext.setVisibility(ImageButton.INVISIBLE);
-        if (mAudioController.hasPrevious())
+        if (mClient.hasPrevious())
             mPrevious.setVisibility(ImageButton.VISIBLE);
         else
             mPrevious.setVisibility(ImageButton.INVISIBLE);
@@ -383,9 +399,11 @@ public class AudioPlayer extends Fragment implements PlaybackServiceClient.Callb
     }
 
     @Override
-    public synchronized void updateProgress() {
-        int time = mAudioController.getTime();
-        int length = mAudioController.getLength();
+    public void updateProgress() {
+        if (!mClient.isConnected())
+            return;
+        int time = mClient.getTime();
+        int length = mClient.getLength();
 
         mHeaderTime.setText(Strings.millisToString(time));
         mLength.setText(Strings.millisToString(length));
@@ -409,11 +427,13 @@ public class AudioPlayer extends Fragment implements PlaybackServiceClient.Callb
 
     private void updateList() {
         int currentIndex = -1;
+        if (!mClient.isConnected())
+            return;
 
         mSongsListAdapter.clear();
 
-        final List<MediaWrapper> audioList = mAudioController.getMedias();
-        final String currentItem = mAudioController.getCurrentMediaLocation();
+        final List<MediaWrapper> audioList = mClient.getMedias();
+        final String currentItem = mClient.getCurrentMediaLocation();
 
         if (audioList != null) {
             for (int i = 0; i < audioList.size(); i++) {
@@ -445,9 +465,9 @@ public class AudioPlayer extends Fragment implements PlaybackServiceClient.Callb
 
         @Override
         public void onProgressChanged(SeekBar sb, int prog, boolean fromUser) {
-            if (fromUser) {
-                mAudioController.setTime(prog);
-                mTime.setText(Strings.millisToString(mShowRemainingTime ? prog-mAudioController.getLength() : prog));
+            if (fromUser && mClient.isConnected()) {
+                mClient.setTime(prog);
+                mTime.setText(Strings.millisToString(mShowRemainingTime ? prog- mClient.getLength() : prog));
                 mHeaderTime.setText(Strings.millisToString(prog));
         }
     }
@@ -459,43 +479,52 @@ public class AudioPlayer extends Fragment implements PlaybackServiceClient.Callb
     }
 
     public void onPlayPauseClick(View view) {
-        if (mAudioController.isPlaying()) {
-            mAudioController.pause();
+        if (!mClient.isConnected())
+            return;
+        if (mClient.isPlaying()) {
+            mClient.pause();
         } else {
-            mAudioController.play();
+            mClient.play();
         }
     }
 
     public void onStopClick(View view) {
-        mAudioController.stop();
+        if (mClient.isConnected())
+            mClient.stop();
     }
 
     public void onNextClick(View view) {
-        mAudioController.next();
+        if (mClient.isConnected())
+            mClient.next();
     }
 
     public void onPreviousClick(View view) {
-        mAudioController.previous();
+        if (mClient.isConnected())
+            mClient.previous();
     }
 
     public void onRepeatClick(View view) {
-        switch (mAudioController.getRepeatType()) {
+        if (!mClient.isConnected())
+            return;
+
+        switch (mClient.getRepeatType()) {
             case None:
-                mAudioController.setRepeatType(PlaybackService.RepeatType.All);
+                mClient.setRepeatType(PlaybackService.RepeatType.All);
                 break;
             case All:
-                mAudioController.setRepeatType(PlaybackService.RepeatType.Once);
+                mClient.setRepeatType(PlaybackService.RepeatType.Once);
                 break;
             default:
             case Once:
-                mAudioController.setRepeatType(PlaybackService.RepeatType.None);
+                mClient.setRepeatType(PlaybackService.RepeatType.None);
                 break;
         }
         update();
     }
 
     public void onShuffleClick(View view) {
-        mAudioController.shuffle();
+        if (mClient.isConnected())
+            mClient.shuffle();
         update();
     }
 
@@ -562,10 +591,12 @@ public class AudioPlayer extends Fragment implements PlaybackServiceClient.Callb
 
         @Override
         public void onMediaSwitched(int position) {
+            if (!mClient.isConnected())
+                return;
             if (position == AudioMediaSwitcherListener.PREVIOUS_MEDIA)
-                mAudioController.previous();
+                mClient.previous();
             else if (position == AudioMediaSwitcherListener.NEXT_MEDIA)
-                mAudioController.next();
+                mClient.next();
         }
 
         @Override
@@ -592,10 +623,12 @@ public class AudioPlayer extends Fragment implements PlaybackServiceClient.Callb
 
         @Override
         public void onMediaSwitched(int position) {
+            if (!mClient.isConnected())
+                return;
             if (position == AudioMediaSwitcherListener.PREVIOUS_MEDIA)
-                mAudioController.previous();
+                mClient.previous();
             else if (position == AudioMediaSwitcherListener.NEXT_MEDIA)
-                mAudioController.next();
+                mClient.next();
         }
 
         @Override
@@ -612,10 +645,12 @@ public class AudioPlayer extends Fragment implements PlaybackServiceClient.Callb
     public void onClick(View v) {
         switch (v.getId()){
             case R.id.playlist_save:
+                if (!mClient.isConnected())
+                    return;
                 FragmentManager fm = getActivity().getSupportFragmentManager();
                 SavePlaylistDialog savePlaylistDialog = new SavePlaylistDialog();
                 Bundle args = new Bundle();
-                args.putParcelableArrayList(SavePlaylistDialog.KEY_TRACKS, (ArrayList<MediaWrapper>) mAudioController.getMedias());
+                args.putParcelableArrayList(SavePlaylistDialog.KEY_TRACKS, (ArrayList<MediaWrapper>) mClient.getMedias());
                 savePlaylistDialog.setArguments(args);
                 savePlaylistDialog.show(fm, "fragment_save_playlist");
                 break;
@@ -666,14 +701,16 @@ public class AudioPlayer extends Fragment implements PlaybackServiceClient.Callb
 
         @Override
         public boolean onTouch(View v, MotionEvent event) {
+            if (!mClient.isConnected())
+                return false;
             switch(event.getAction()) {
             case MotionEvent.ACTION_DOWN:
                 (forward ? mNext : mPrevious).setImageResource(this.pressed);
 
-                possibleSeek = mAudioController.getTime();
+                possibleSeek = mClient.getTime();
                 mPreviewingSeek = true;
                 vibrated = false;
-                length = mAudioController.getLength();
+                length = mClient.getLength();
 
                 h.postDelayed(seekRunnable, 1000);
                 return true;
@@ -690,13 +727,13 @@ public class AudioPlayer extends Fragment implements PlaybackServiceClient.Callb
                         onPreviousClick(v);
                 } else {
                     if(forward) {
-                        if(possibleSeek < mAudioController.getLength())
-                            mAudioController.setTime(possibleSeek);
+                        if(possibleSeek < mClient.getLength())
+                            mClient.setTime(possibleSeek);
                         else
                             onNextClick(v);
                     } else {
                         if(possibleSeek > 0)
-                            mAudioController.setTime(possibleSeek);
+                            mClient.setTime(possibleSeek);
                         else
                             onPreviousClick(v);
                     }
diff --git a/vlc-android/src/org/videolan/vlc/gui/browser/BaseBrowserFragment.java b/vlc-android/src/org/videolan/vlc/gui/browser/BaseBrowserFragment.java
index 350da67..d55715c 100644
--- a/vlc-android/src/org/videolan/vlc/gui/browser/BaseBrowserFragment.java
+++ b/vlc-android/src/org/videolan/vlc/gui/browser/BaseBrowserFragment.java
@@ -51,6 +51,7 @@ import org.videolan.libvlc.util.AndroidUtil;
 import org.videolan.libvlc.util.MediaBrowser;
 import org.videolan.vlc.MediaLibrary;
 import org.videolan.vlc.MediaWrapper;
+import org.videolan.vlc.PlaybackService;
 import org.videolan.vlc.PlaybackServiceClient;
 import org.videolan.vlc.R;
 import org.videolan.vlc.VLCApplication;
@@ -403,9 +404,10 @@ public abstract class BaseBrowserFragment extends MediaBrowserFragment implement
             case R.id.directory_view_play:
                 Util.openMedia(getActivity(), (MediaWrapper) mAdapter.getItem(position));
                 return true;
-            case R.id.directory_view_append:
-                PlaybackServiceClient.getInstance().append(mw);
+            case R.id.directory_view_append: {
+                PlaybackServiceClient.Async.append(getActivity(), null, mw);
                 return true;
+            }
             case R.id.directory_view_delete:
                 AlertDialog alertDialog = CommonDialogs.deleteMedia(
                         mw.getType(), getActivity(), mw.getLocation(),
@@ -423,9 +425,10 @@ public abstract class BaseBrowserFragment extends MediaBrowserFragment implement
                 i.putExtra("param", mw.getUri().toString());
                 startActivity(i);
                 return true;
-            case R.id.directory_view_play_audio:
-                PlaybackServiceClient.getInstance().load(mw);
+            case R.id.directory_view_play_audio: {
+                PlaybackServiceClient.Async.load(getActivity(), null, mw);
                 return true;
+            }
             case  R.id.directory_view_play_video:
                 VideoPlayerActivity.start(getActivity(), mw.getUri());
                 return true;
diff --git a/vlc-android/src/org/videolan/vlc/gui/video/VideoGridFragment.java b/vlc-android/src/org/videolan/vlc/gui/video/VideoGridFragment.java
index 50e9284..d732b65 100644
--- a/vlc-android/src/org/videolan/vlc/gui/video/VideoGridFragment.java
+++ b/vlc-android/src/org/videolan/vlc/gui/video/VideoGridFragment.java
@@ -113,7 +113,6 @@ public class VideoGridFragment extends MediaBrowserFragment implements ISortable
     private VideoGridAnimator mAnimator;
 
     private MainActivity mMainActivity;
-    private PlaybackServiceClient mAudioController;
 
     // Gridview position saved in onPause()
     private int mGVFirstVisiblePos;
@@ -125,8 +124,6 @@ public class VideoGridFragment extends MediaBrowserFragment implements ISortable
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
-        mAudioController = PlaybackServiceClient.getInstance();
-
         mVideoAdapter = new VideoListAdapter(this);
         mMediaLibrary = MediaLibrary.getInstance();
 
@@ -137,6 +134,7 @@ public class VideoGridFragment extends MediaBrowserFragment implements ISortable
         if (activity != null)
             mThumbnailer = new Thumbnailer(activity, activity.getWindowManager().getDefaultDisplay());
     }
+
     @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
 
@@ -186,8 +184,6 @@ public class VideoGridFragment extends MediaBrowserFragment implements ISortable
     @Override
     public void onPause() {
         super.onPause();
-        if (!(getActivity() instanceof MainActivity))
-            PlaybackServiceClient.getInstance().unbindAudioService(getActivity());
         mGVFirstVisiblePos = mGridView.getFirstVisiblePosition();
         mMediaLibrary.setBrowser(null);
         mMediaLibrary.removeUpdateHandler(mHandler);
@@ -200,9 +196,7 @@ public class VideoGridFragment extends MediaBrowserFragment implements ISortable
     @Override
     public void onResume() {
         super.onResume();
-        if (!(getActivity() instanceof MainActivity))
-            PlaybackServiceClient.getInstance().bindAudioService(getActivity());
-        else
+        if ((getActivity() instanceof MainActivity))
             mMainActivity = (MainActivity) getActivity();
         mMediaLibrary.setBrowser(this);
         mMediaLibrary.addUpdateHandler(mHandler);
@@ -307,7 +301,7 @@ public class VideoGridFragment extends MediaBrowserFragment implements ISortable
     }
 
     protected void playAudio(MediaWrapper media) {
-        mAudioController.load(media, true);
+        PlaybackServiceClient.Async.load(getActivity(), null, media, true);
     }
 
     private boolean handleContextItemSelected(MenuItem menu, int position) {
@@ -571,7 +565,7 @@ public class VideoGridFragment extends MediaBrowserFragment implements ISortable
     }
 
     public void deleteMedia(int position){
-        MediaWrapper media = mVideoAdapter.getItem(position);
+        final MediaWrapper media = mVideoAdapter.getItem(position);
         final String path = media.getUri().getPath();
         new Thread(new Runnable() {
             public void run() {
@@ -580,8 +574,17 @@ public class VideoGridFragment extends MediaBrowserFragment implements ISortable
         }).start();
         mMediaLibrary.getMediaItems().remove(media);
         mVideoAdapter.remove(media);
-        if (mAudioController.getMediaLocations().contains(media.getLocation()))
-            mAudioController.removeLocation(media.getLocation());
+        PlaybackServiceClient.Async.getMediaLocations(getActivity(), new PlaybackServiceClient.AsyncCallback<List<String>>() {
+            @Override
+            public void onResult(PlaybackServiceClient client, List<String> result) {
+                if (result != null && result.contains(media.getLocation()))
+                    client.removeLocation(media.getLocation());
+            }
+
+            @Override
+            public void onError(PlaybackServiceClient client) {
+            }
+        });
     }
 
 
diff --git a/vlc-android/src/org/videolan/vlc/gui/video/VideoPlayerActivity.java b/vlc-android/src/org/videolan/vlc/gui/video/VideoPlayerActivity.java
index d1bcdda..439028c 100644
--- a/vlc-android/src/org/videolan/vlc/gui/video/VideoPlayerActivity.java
+++ b/vlc-android/src/org/videolan/vlc/gui/video/VideoPlayerActivity.java
@@ -160,6 +160,7 @@ public class VideoPlayerActivity extends AppCompatActivity implements IVideoPlay
     public final static int RESULT_HARDWARE_ACCELERATION_ERROR = RESULT_FIRST_USER + 3;
     public final static int RESULT_VIDEO_TRACK_LOST = RESULT_FIRST_USER + 4;
 
+    private PlaybackServiceClient mClient;
     private SurfaceView mSurfaceView;
     private SurfaceView mSubtitlesSurfaceView;
     private SurfaceHolder mSurfaceHolder;
@@ -304,9 +305,6 @@ public class VideoPlayerActivity extends AppCompatActivity implements IVideoPlay
     private boolean mLostFocus = false;
     private boolean mHasAudioFocus = false;
 
-    /* Flag to indicate if AudioService is bound or binding */
-    private boolean mBound = false;
-
     // Tips
     private View mOverlayTips;
     private static final String PREF_TIPS_SHOWN = "video_player_tips_shown";
@@ -322,7 +320,7 @@ public class VideoPlayerActivity extends AppCompatActivity implements IVideoPlay
     private OnLayoutChangeListener mOnLayoutChangeListener;
     private AlertDialog mAlertDialog;
 
-    private boolean mAudioServiceReady = false;
+    private boolean mPlaybackServiceReady = false;
     private boolean mSurfaceReady = false;
     private boolean mSubtitleSurfaceReady = false;
 
@@ -362,6 +360,8 @@ public class VideoPlayerActivity extends AppCompatActivity implements IVideoPlay
             Log.d(TAG, "MediaRouter information : " + mMediaRouter  .toString());
         }
 
+        mClient = new PlaybackServiceClient(this, mPlaybackServiceCallback);
+
         mSettings = PreferenceManager.getDefaultSharedPreferences(this);
 
         /* Services and miscellaneous */
@@ -563,8 +563,6 @@ public class VideoPlayerActivity extends AppCompatActivity implements IVideoPlay
         mTime.setOnClickListener(mRemainingTimeListener);
         mSize.setOnClickListener(mSizeListener);
 
-        bindAudioService();
-
         if (mIsLocked && mScreenOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR)
             setRequestedOrientation(mScreenOrientationLock);
     }
@@ -606,10 +604,18 @@ public class VideoPlayerActivity extends AppCompatActivity implements IVideoPlay
         mOverlayButtons.setLayoutParams(layoutParams);
     }
 
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        mClient.connect();
+    }
+
     @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
     @Override
     protected void onStop() {
         super.onStop();
+        mClient.disconnect();
 
         if (mAlertDialog != null && mAlertDialog.isShowing())
             mAlertDialog.dismiss();
@@ -652,32 +658,6 @@ public class VideoPlayerActivity extends AppCompatActivity implements IVideoPlay
         mAudioManager = null;
     }
 
-    private void bindAudioService() {
-        if (mBound)
-            return;
-        mBound = true;
-        PlaybackServiceClient.getInstance().bindAudioService(this,
-                new PlaybackServiceClient.AudioServiceConnectionListener() {
-                    @Override
-                    public void onConnectionSuccess() {
-                        mAudioServiceReady = true;
-                        mHandler.sendEmptyMessage(START_PLAYBACK);
-                    }
-
-                    @Override
-                    public void onConnectionFailed() {
-                        mBound = false;
-                        mAudioServiceReady = false;
-                        mHandler.sendEmptyMessage(AUDIO_SERVICE_CONNECTION_FAILED);
-                    }
-                });
-    }
-    private void unbindAudioService() {
-        PlaybackServiceClient.getInstance().unbindAudioService(this);
-        mAudioServiceReady = false;
-        mBound = false;
-    }
-
     /**
      * Add or remove MediaRouter callbacks. This is provided for version targeting.
      *
@@ -696,7 +676,7 @@ public class VideoPlayerActivity extends AppCompatActivity implements IVideoPlay
     @TargetApi(Build.VERSION_CODES.HONEYCOMB)
     private void startPlayback() {
         /* start playback only when audio service and both surfaces are ready */
-        if (mPlaybackStarted || !mAudioServiceReady || !mSurfaceReady || !mSubtitleSurfaceReady)
+        if (mPlaybackStarted || !mPlaybackServiceReady || !mSurfaceReady || !mSubtitleSurfaceReady)
             return;
 
         mPlaybackStarted = true;
@@ -750,8 +730,7 @@ public class VideoPlayerActivity extends AppCompatActivity implements IVideoPlay
 
         if(mSwitchingView) {
             Log.d(TAG, "mLocation = \"" + mUri + "\"");
-            PlaybackServiceClient.getInstance().showWithoutParse(savedIndexPosition);
-            unbindAudioService();
+            mClient.showWithoutParse(savedIndexPosition);
             return;
         }
 
@@ -820,8 +799,6 @@ public class VideoPlayerActivity extends AppCompatActivity implements IVideoPlay
 
         if (AndroidUtil.isHoneycombOrLater() && mOnLayoutChangeListener != null)
             mSurfaceFrame.removeOnLayoutChangeListener(mOnLayoutChangeListener);
-
-        unbindAudioService();
     }
 
     @Override
@@ -2799,7 +2776,7 @@ public class VideoPlayerActivity extends AppCompatActivity implements IVideoPlay
             savedIndexPosition = openedPosition;
         } else {
             /* prepare playback */
-            PlaybackServiceClient.getInstance().stop(); // Stop the previous playback.
+            mClient.stop();
             if (savedIndexPosition == -1 && mUri != null) {
                 mMediaListPlayer.getMediaList().clear();
                 final Media media = new Media(LibVLC(), mUri);
@@ -3194,4 +3171,35 @@ public class VideoPlayerActivity extends AppCompatActivity implements IVideoPlay
             return false;
         }
     };
+
+    private PlaybackServiceClient.Callback mPlaybackServiceCallback= new PlaybackServiceClient.Callback() {
+
+        @Override
+        public void onConnected() {
+            mPlaybackServiceReady = true;
+            mHandler.sendEmptyMessage(START_PLAYBACK);
+        }
+
+        @Override
+        public void onDisconnected() {
+            mPlaybackServiceReady = false;
+            mHandler.sendEmptyMessage(AUDIO_SERVICE_CONNECTION_FAILED);
+        }
+
+        @Override
+        public void update() {
+        }
+
+        @Override
+        public void updateProgress() {
+        }
+
+        @Override
+        public void onMediaPlayedAdded(MediaWrapper media, int index) {
+        }
+
+        @Override
+        public void onMediaPlayedRemoved(int index) {
+        }
+    };
 }
diff --git a/vlc-android/src/org/videolan/vlc/util/Util.java b/vlc-android/src/org/videolan/vlc/util/Util.java
index 055aff6..f97e0d7 100644
--- a/vlc-android/src/org/videolan/vlc/util/Util.java
+++ b/vlc-android/src/org/videolan/vlc/util/Util.java
@@ -207,7 +207,7 @@ public class Util {
     }
 
 
-    public static void openMedia(Context context, final MediaWrapper media){
+    public static void openMedia(final Context context, final MediaWrapper media){
         if (media == null)
             return;
         if (media.getType() == MediaWrapper.TYPE_VIDEO)
@@ -216,45 +216,40 @@ public class Util {
             VLCCallbackTask task = new VLCCallbackTask(context) {
                 @Override
                 public void run() {
-                    PlaybackServiceClient c = PlaybackServiceClient.getInstance();
-                    c.load(media);
+                    PlaybackServiceClient.Async.load(context, null, media);
                 }
             };
             task.execute();
         }
     }
 
-    public static  void openList(Context context, final List<MediaWrapper> list, final int position){
+    public static  void openList(final Context context, final List<MediaWrapper> list, final int position){
         VLCCallbackTask task = new VLCCallbackTask(context){
             @Override
             public void run() {
-                PlaybackServiceClient c = PlaybackServiceClient.getInstance();
-
                       /* Use the audio player by default. If a video track is
                        * detected, then it will automatically switch to the video
                        * player. This allows us to support more types of streams
                        * (for example, RTSP and TS streaming) where ES can be
                        * dynamically adapted rather than a simple scan.
                        */
-                c.load(list, position);
+                PlaybackServiceClient.Async.load(context, null, list, position);
             }
         };
         task.execute();
     }
 
-    public static void openStream(Context context, final String uri){
+    public static void openStream(final Context context, final String uri){
         VLCCallbackTask task = new VLCCallbackTask(context){
             @Override
             public void run() {
-                PlaybackServiceClient c = PlaybackServiceClient.getInstance();
-
                       /* Use the audio player by default. If a video track is
                        * detected, then it will automatically switch to the video
                        * player. This allows us to support more types of streams
                        * (for example, RTSP and TS streaming) where ES can be
                        * dynamically adapted rather than a simple scan.
                        */
-                c.loadLocation(uri);
+                PlaybackServiceClient.Async.loadLocation(context, null, uri);
             }
         };
         task.execute();
diff --git a/vlc-android/src/org/videolan/vlc/widget/AudioMediaSwitcher.java b/vlc-android/src/org/videolan/vlc/widget/AudioMediaSwitcher.java
index 0afd6ff..c7c4bb2 100644
--- a/vlc-android/src/org/videolan/vlc/widget/AudioMediaSwitcher.java
+++ b/vlc-android/src/org/videolan/vlc/widget/AudioMediaSwitcher.java
@@ -41,36 +41,32 @@ public abstract class AudioMediaSwitcher extends FlingViewGroup {
         setOnViewSwitchedListener(mViewSwitchListener);
     }
 
-    public void updateMedia() {
-        PlaybackServiceClient audioController = PlaybackServiceClient.getInstance();
-        if (audioController == null)
-            return;
-
+    public void updateMedia(PlaybackServiceClient client) {
         removeAllViews();
 
         hasPrevious = false;
         previousPosition = 0;
 
         LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-        if (audioController.hasPrevious()) {
+        if (client.hasPrevious()) {
             addMediaView(inflater,
-                    audioController.getTitlePrev(),
-                    audioController.getArtistPrev(),
-                    audioController.getCoverPrev());
+                    client.getTitlePrev(),
+                    client.getArtistPrev(),
+                    client.getCoverPrev());
             hasPrevious = true;
         }
-        if (audioController.hasMedia())
+        if (client.hasMedia())
             addMediaView(inflater,
-                    audioController.getTitle(),
-                    audioController.getArtist(),
-                    audioController.getCover());
-        if (audioController.hasNext())
+                    client.getTitle(),
+                    client.getArtist(),
+                    client.getCover());
+        if (client.hasNext())
             addMediaView(inflater,
-                    audioController.getTitleNext(),
-                    audioController.getArtistNext(),
-                    audioController.getCoverNext());
+                    client.getTitleNext(),
+                    client.getArtistNext(),
+                    client.getCoverNext());
 
-        if (audioController.hasPrevious() && audioController.hasMedia()) {
+        if (client.hasPrevious() && client.hasMedia()) {
             previousPosition = 1;
             scrollTo(1);
         }
diff --git a/vlc-android/tv/src/org/videolan/vlc/gui/tv/MediaItemDetailsFragment.java b/vlc-android/tv/src/org/videolan/vlc/gui/tv/MediaItemDetailsFragment.java
index 718bcc0..8baab7c 100644
--- a/vlc-android/tv/src/org/videolan/vlc/gui/tv/MediaItemDetailsFragment.java
+++ b/vlc-android/tv/src/org/videolan/vlc/gui/tv/MediaItemDetailsFragment.java
@@ -46,7 +46,7 @@ import android.support.v17.leanback.widget.ListRowPresenter;
 import android.support.v17.leanback.widget.OnActionClickedListener;
 import android.widget.Toast;
 
-public class MediaItemDetailsFragment extends DetailsFragment implements PlaybackServiceClient.AudioServiceConnectionListener {
+public class MediaItemDetailsFragment extends DetailsFragment implements PlaybackServiceClient.Callback {
     private static final String TAG = "MediaItemDetailsFragment";
     private static final int ID_PLAY = 1;
     private static final int ID_LISTEN = 2;
@@ -54,7 +54,7 @@ public class MediaItemDetailsFragment extends DetailsFragment implements Playbac
     private static final int ID_FAVORITE_DELETE = 4;
     private static final int ID_BROWSE = 5;
     private ArrayObjectAdapter mRowsAdapter;
-    private PlaybackServiceClient mAudioController;
+    private PlaybackServiceClient mClient;
     private MediaItemDetails mMedia;
     private MediaWrapper mMediaWrapper;
     private MediaDatabase mDb;
@@ -63,19 +63,20 @@ public class MediaItemDetailsFragment extends DetailsFragment implements Playbac
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
-        mAudioController = PlaybackServiceClient.getInstance();
+        mClient = new PlaybackServiceClient(getActivity(), this);
         buildDetails();
     }
 
-    public void onResume(){
-        super.onResume();
+    @Override
+    public void onStop() {
+        super.onStop();
+        mClient.disconnect();
     }
 
     public void onPause(){
         super.onPause();
-        if (mAudioController.isPlaying()){
-            mAudioController.stop();
-            mAudioController.unbindAudioService(getActivity());
+        if (mClient.isConnected() && mClient.isPlaying()){
+            mClient.stop();
         }
     }
 
@@ -105,7 +106,7 @@ public class MediaItemDetailsFragment extends DetailsFragment implements Playbac
             public void onActionClicked(Action action) {
                 switch ((int)action.getId()){
                     case ID_LISTEN:
-                        mAudioController.bindAudioService(getActivity(), MediaItemDetailsFragment.this);
+                        mClient.connect();
                         break;
                     case ID_PLAY:
                         ArrayList<MediaWrapper> tracks = new ArrayList<MediaWrapper>();
@@ -165,11 +166,27 @@ public class MediaItemDetailsFragment extends DetailsFragment implements Playbac
     }
 
     @Override
-    public void onConnectionSuccess() {
-        mAudioController.load(mMediaWrapper);
+    public void onConnected() {
+        mClient.load(mMediaWrapper);
     }
 
     @Override
-    public void onConnectionFailed() {}
+    public void onDisconnected() {
+    }
 
+    @Override
+    public void update() {
+    }
+
+    @Override
+    public void updateProgress() {
+    }
+
+    @Override
+    public void onMediaPlayedAdded(MediaWrapper media, int index) {
+    }
+
+    @Override
+    public void onMediaPlayedRemoved(int index) {
+    }
 }
diff --git a/vlc-android/tv/src/org/videolan/vlc/gui/tv/audioplayer/AudioPlayerActivity.java b/vlc-android/tv/src/org/videolan/vlc/gui/tv/audioplayer/AudioPlayerActivity.java
index 15db87f..b2f003f 100644
--- a/vlc-android/tv/src/org/videolan/vlc/gui/tv/audioplayer/AudioPlayerActivity.java
+++ b/vlc-android/tv/src/org/videolan/vlc/gui/tv/audioplayer/AudioPlayerActivity.java
@@ -46,12 +46,12 @@ import android.widget.ImageView;
 import android.widget.ProgressBar;
 import android.widget.TextView;
 
-public class AudioPlayerActivity extends Activity implements PlaybackServiceClient.AudioServiceConnectionListener, PlaybackServiceClient.Callback, View.OnFocusChangeListener {
+public class AudioPlayerActivity extends Activity implements PlaybackServiceClient.Callback, View.OnFocusChangeListener {
     public static final String TAG = "VLC/AudioPlayerActivity";
 
     public static final String MEDIA_LIST = "media_list";
 
-    private PlaybackServiceClient mAudioController;
+    private PlaybackServiceClient mClient;
     private RecyclerView mRecyclerView;
     private PlaylistAdapter mAdapter;
     private LinearLayoutManager mLayoutManager;
@@ -83,9 +83,9 @@ public class AudioPlayerActivity extends Activity implements PlaybackServiceClie
         mAdapter = new PlaylistAdapter(this, mMediaList);
         mRecyclerView.setAdapter(mAdapter);
 
-        mAudioController = PlaybackServiceClient.getInstance();
+        mClient = new PlaybackServiceClient(this, this);
 
-        mAudioController.getRepeatType();
+        mClient.getRepeatType();
         mTitleTv = (TextView)findViewById(R.id.media_title);
         mArtistTv = (TextView)findViewById(R.id.media_artist);
         mNext = (ImageView)findViewById(R.id.button_next);
@@ -97,17 +97,16 @@ public class AudioPlayerActivity extends Activity implements PlaybackServiceClie
         findViewById(R.id.button_shuffle).setOnFocusChangeListener(this);
     }
 
-    public void onStart(){
+    @Override
+    protected void onStart() {
         super.onStart();
-        mAudioController.bindAudioService(this, this);
-        mAudioController.addCallback(this);
+        mClient.connect();
     }
 
-    public void onStop(){
+    @Override
+    protected void onStop() {
         super.onStop();
-        mAudioController.removeCallback(this);
-        mAudioController.unbindAudioService(this);
-        mMediaList.clear();
+        mClient.disconnect();
     }
 
     protected void onResume() {
@@ -121,10 +120,10 @@ public class AudioPlayerActivity extends Activity implements PlaybackServiceClie
     };
 
     @Override
-    public void onConnectionSuccess() {
-        ArrayList<MediaWrapper> medias = (ArrayList<MediaWrapper>) mAudioController.getMedias();
+    public void onConnected() {
+        ArrayList<MediaWrapper> medias = (ArrayList<MediaWrapper>) mClient.getMedias();
         if (!mMediaList.isEmpty() && !mMediaList.equals(medias)) {
-            mAudioController.load(mMediaList, 0);
+            mClient.load(mMediaList, 0);
         } else {
             mMediaList = medias;
             update();
@@ -134,19 +133,22 @@ public class AudioPlayerActivity extends Activity implements PlaybackServiceClie
     }
 
     @Override
-    public void onConnectionFailed() {}
+    public void onDisconnected() {
+    }
 
     @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());
-            MediaWrapper MediaWrapper = MediaLibrary.getInstance().getMediaItem(mAudioController.getCurrentMediaLocation());
+        if (!mClient.isConnected())
+            return;
+        mPlayPauseButton.setImageResource(mClient.isPlaying() ? R.drawable.ic_pause : R.drawable.ic_play);
+        if (mClient.hasMedia()) {
+            mTitleTv.setText(mClient.getTitle());
+            mArtistTv.setText(mClient.getArtist());
+            mProgressBar.setMax(mClient.getLength());
+            MediaWrapper MediaWrapper = MediaLibrary.getInstance().getMediaItem(mClient.getCurrentMediaLocation());
             Bitmap cover = AudioUtil.getCover(this, MediaWrapper, mCover.getWidth());
             if (cover == null)
-                cover = mAudioController.getCover();
+                cover = mClient.getCover();
             if (cover == null)
                 mCover.setImageResource(R.drawable.background_cone);
             else
@@ -156,7 +158,17 @@ public class AudioPlayerActivity extends Activity implements PlaybackServiceClie
 
     @Override
     public void updateProgress() {
-        mProgressBar.setProgress(mAudioController.getTime());
+        mProgressBar.setProgress(mClient.getTime());
+    }
+
+    @Override
+    public void onMediaPlayedAdded(MediaWrapper media, int index) {
+
+    }
+
+    @Override
+    public void onMediaPlayedRemoved(int index) {
+
     }
 
     public boolean onKeyDown(int keyCode, KeyEvent event){
@@ -207,7 +219,7 @@ public class AudioPlayerActivity extends Activity implements PlaybackServiceClie
     }
 
     public void playSelection() {
-        mAudioController.playIndex(mAdapter.getmSelectedItem());
+        mClient.playIndex(mAdapter.getmSelectedItem());
         mCurrentlyPlaying = mAdapter.getmSelectedItem();
     }
 
@@ -239,10 +251,10 @@ public class AudioPlayerActivity extends Activity implements PlaybackServiceClie
     }
 
     private void seek(int delta) {
-        int time = mAudioController.getTime()+delta;
-        if (time < 0 || time > mAudioController.getLength())
+        int time = mClient.getTime()+delta;
+        if (time < 0 || time > mClient.getLength())
             return;
-        mAudioController.setTime(time);
+        mClient.setTime(time);
     }
 
     public void onClick(View v){
@@ -266,53 +278,55 @@ public class AudioPlayerActivity extends Activity implements PlaybackServiceClie
     }
 
     private void setShuffleMode(boolean shuffle) {
+        if (!mClient.isConnected())
+            return;
         mShuffling = shuffle;
         mShuffle.setImageResource(shuffle ? R.drawable.ic_shuffle_on :
                 R.drawable.ic_shuffle);
-        ArrayList<MediaWrapper> medias = (ArrayList<MediaWrapper>) mAudioController.getMedias();
+        ArrayList<MediaWrapper> medias = (ArrayList<MediaWrapper>) mClient.getMedias();
         if (shuffle){
             Collections.shuffle(medias);
         } else {
             Collections.sort(medias, MediaComparators.byTrackNumber);
         }
-        mAudioController.load(medias, 0);
+        mClient.load(medias, 0);
         mAdapter.updateList(medias);
         update();
     }
 
     private void updateRepeatMode() {
-        PlaybackService.RepeatType type = mAudioController.getRepeatType();
+        PlaybackService.RepeatType type = mClient.getRepeatType();
         if (type == PlaybackService.RepeatType.None){
-            mAudioController.setRepeatType(PlaybackService.RepeatType.All);
+            mClient.setRepeatType(PlaybackService.RepeatType.All);
             mRepeat.setImageResource(R.drawable.ic_repeat_on);
         } else if (type == PlaybackService.RepeatType.All) {
-            mAudioController.setRepeatType(PlaybackService.RepeatType.Once);
+            mClient.setRepeatType(PlaybackService.RepeatType.Once);
             mRepeat.setImageResource(R.drawable.ic_repeat_one);
         } else if (type == PlaybackService.RepeatType.Once) {
-            mAudioController.setRepeatType(PlaybackService.RepeatType.None);
+            mClient.setRepeatType(PlaybackService.RepeatType.None);
             mRepeat.setImageResource(R.drawable.ic_repeat);
         }
     }
 
     private void goPrevious() {
-        if (mAudioController.hasPrevious()) {
-            mAudioController.previous();
+        if (mClient.hasPrevious()) {
+            mClient.previous();
             selectItem(--mCurrentlyPlaying);
         }
     }
 
     private void goNext() {
-        if (mAudioController.hasNext()){
-            mAudioController.next();
+        if (mClient.hasNext()){
+            mClient.next();
             selectItem(++mCurrentlyPlaying);
         }
     }
 
     private void togglePlayPause() {
-        if (mAudioController.isPlaying())
-            mAudioController.pause();
-        else if (mAudioController.hasMedia())
-            mAudioController.play();
+        if (mClient.isPlaying())
+            mClient.pause();
+        else if (mClient.hasMedia())
+            mClient.play();
     }
 
     private void selectNext() {
-- 
2.1.4



More information about the Android mailing list