Android Open Source - SoundBox Media Player Service






From Project

Back to project page SoundBox.

License

The source code is released under:

GNU General Public License

If you think the Android project SoundBox listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

/*
 * SoundBox - Android Music Player/* ww w .  j  a va 2 s . c om*/
 * Copyright (C) 2013 Ivn Arcuschin Moreno
 *
 * This file is part of SoundBox.
 *
 * SoundBox is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * SoundBox is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with SoundBox.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.arcusapp.soundbox.player;

import android.app.AlarmManager;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnCompletionListener;
import android.os.Binder;
import android.os.Bundle;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.SystemClock;
import android.util.Log;

import com.arcusapp.soundbox.data.SoundBoxPreferences;
import com.arcusapp.soundbox.model.BundleExtra;
import com.arcusapp.soundbox.model.MediaPlayerServiceListener;
import com.arcusapp.soundbox.model.RandomState;
import com.arcusapp.soundbox.model.RepeatState;
import com.arcusapp.soundbox.model.Song;

import java.util.ArrayList;
import java.util.List;

public class MediaPlayerService extends Service implements OnCompletionListener {

    private static final String TAG = "MediaPlayerService";
    public static final String INCOMMING_CALL = "com.arcusapp.soundbox.action.MEDIA_PLAYER_SERVICE.INCOMMING_CALL";
    public static final String PLAY_NEW_SONGS = "com.arcusapp.soundbox.action.MEDIA_PLAYER_SERVICE.PLAY_NEW_SONGS";
    public static final String CHANGE_FOREGROUND_STATE = "com.arcusapp.soundbox.player.MEDIA_PLAYER_SERVICE.CHANGE_FOREGROUND_STATE";
    public static final String NOW_IN_FOREGROUND = "now_in_foreground";

    /**
     * Possible actions that can be called from the MediaPlayerNotification
     */
    public static final String TOGGLEPLAYPAUSE_ACTION = "com.arcusapp.soundbox.action.MEDIA_PLAYER_SERVICE.TOGGLEPLAYPAUSE";
    public static final String PREVIOUS_ACTION = "com.arcusapp.soundbox.player.MEDIA_PLAYER_SERVICE.PRVIOUS";
    public static final String NEXT_ACTION = "com.arcusapp.soundbox.player.MEDIA_PLAYER_SERVICE.NEXT";
    public static final String STOP_ACTION = "com.arcusapp.soundbox.player.MEDIA_PLAYER_SERVICE.STOP";

    /**
     * Idle time before stopping the foreground notfication (1 minute)
     */
    private static final int IDLE_DELAY = 60000;

    private BroadcastReceiver headsetReceiver;

    // private int currentSongPosition;
    private SongStack currentSongStack;
    private List<String> songsIDList;

    private RepeatState repeatState = RepeatState.Off;
    private RandomState randomState = RandomState.Off;

    private MediaPlayer mediaPlayer;
    private List<MediaPlayerServiceListener> currentListeners;
    private final IBinder mBinder = new MyBinder();

    private boolean isOnForeground = false;
    MediaPlayerNotification mNotification;

    /**
     * Alarm intent for removing the notification when nothing is playing
     * for some time
     */
    private AlarmManager mAlarmManager;
    private PendingIntent mShutdownIntent;
    private boolean mShutdownScheduled;

    /**
     * Used to know when the service is active
     */
    private boolean mServiceInUse = false;

    /**
     * Used to know if something should be playing or not
     */
    private boolean mIsSupposedToBePlaying = false;

    private int mServiceStartId = -1;

    // Called every time a client starts the service using startService
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        mServiceStartId = startId;

        if(intent != null) {
            handleCommandIntent(intent);
        }

        // Make sure the service will shut down on its own if it was
        // just started but not bound to and nothing is playing
        if(!isPlaying()) {
            Log.d(this.getClass().getName(), "scheduleDelayedShutdown from onStartCommand");
            scheduleDelayedShutdown();
        }

        // We want this service to continue running until it is explicitly stopped, so return sticky.
        return Service.START_STICKY;
    }

    private void handleCommandIntent(Intent intent) {
        String action = intent.getAction();

        // Check if this is an intent from the AudioBecomingNoisyHandler
        if(INCOMMING_CALL.equals(action)) {
            if (mediaPlayer != null) {
                if(mediaPlayer.isPlaying()) {
                    mediaPlayer.pause();
                    mIsSupposedToBePlaying = false;
                    Log.d(this.getClass().getName(), "scheduleDelayedShutdown from INCOMMING_CALL");
                    scheduleDelayedShutdown();
                    fireListenersOnMediaPlayerStateChanged();
                }
            }
        } // Check if we have a request to play new songs
        else if(PLAY_NEW_SONGS.equals(action)) {
            Bundle bundle = intent.getExtras();
            String currentID = BundleExtra.getBundleString(bundle, BundleExtra.CURRENT_ID, BundleExtra.DefaultValues.DEFAULT_ID);
            List<String> songsID = bundle.getStringArrayList(BundleExtra.SONGS_ID_LIST);

            loadSongs(songsID, currentID);
            mediaPlayer.start();
            mIsSupposedToBePlaying = true;
            Log.d(this.getClass().getName(), "cancelShutdown from PLAY_NEW_SONGS");
            cancelShutdown();
        } // Check if the foreground state needs to be changed
        else if (CHANGE_FOREGROUND_STATE.equals(action)) {
            boolean nowInForeground = intent.getBooleanExtra(NOW_IN_FOREGROUND, false);
            if(nowInForeground) {
                // check if we are playing. In case we are not, stop the service
                if(isPlaying()) {
                    Song currentSong = getCurrentSong();
                    Notification notification = mNotification.getNotification(currentSong.getArtist(), currentSong.getAlbum(), currentSong.getTitle(), mIsSupposedToBePlaying);
                    startForeground(MediaPlayerNotification.MEDIA_PLAYER_NOTIFICATION_ID, notification);
                    isOnForeground = true;
                } else {
                    Log.d(this.getClass().getName(), "scheduleDelayedShutdown from CHANGE_FOREGROUND_STATE");
                    scheduleDelayedShutdown();
                }
            } else {
                stopForeground(true);
                isOnForeground = false;
            }
        }

        //Check if there is an action from the MediaPlayerNotification
        if(isOnForeground) {
            if(TOGGLEPLAYPAUSE_ACTION.equals(action)) {
                playAndPause();
            } else if(NEXT_ACTION.equals(action)) {
                playNextSong();
            } else if(PREVIOUS_ACTION.equals(action)) {
                playPreviousSong();
            } else if(STOP_ACTION.equals(action)) {
                stopForeground(true);
                if (!mServiceInUse) {
                    stopSelf(mServiceStartId);
                }
            }
        }
    }

    @Override
    public void onCreate() {
        super.onCreate();

        // Called when the Service object is instantiated. Theoretically, only once.
        if (mediaPlayer == null) {
            mediaPlayer = new MediaPlayer();
            mediaPlayer.setOnCompletionListener(this);
        }
        if(currentListeners == null) {
            currentListeners = new ArrayList<MediaPlayerServiceListener>();
        }
        if(mNotification == null){
            mNotification = new MediaPlayerNotification();
        }

        // Start up the thread running the service. Note that we create a
        // separate thread because the service normally runs in the process's
        // main thread, which we don't want to block. We also make it
        // background priority so CPU-intensive work will not disrupt the UI.
        final HandlerThread thread = new HandlerThread("MusicPlayerHandler",
                android.os.Process.THREAD_PRIORITY_BACKGROUND);
        thread.start();

        FetchLastPlayedSongs();

        if(headsetReceiver == null) {
            headsetReceiver = new BroadcastReceiver() {
                @Override
                public void onReceive(Context context, Intent intent) {
                    // state - 0 for unplugged, 1 for plugged.
                    int state = intent.getIntExtra("state", 0);
                    if(state == 0) {
                        if (mediaPlayer != null) {
                            if(mediaPlayer.isPlaying()) {
                                mediaPlayer.pause();
                                mIsSupposedToBePlaying = false;
                                fireListenersOnMediaPlayerStateChanged();
                            }
                        }
                    }
                }
            };
            registerReceiver(headsetReceiver, new IntentFilter(Intent.ACTION_HEADSET_PLUG));
        }

        // Initialize the delayed shutdown intent
        final Intent shutdownIntent = new Intent(this, MediaPlayerService.class);
        shutdownIntent.setAction(STOP_ACTION);

        mAlarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
        mShutdownIntent = PendingIntent.getService(this, 0, shutdownIntent, 0);

        // Listen for the idle state
        Log.d(this.getClass().getName(), "scheduleDelayedShutdown from onCreate");
        scheduleDelayedShutdown();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();

        // remove any pending alarms
        mAlarmManager.cancel(mShutdownIntent);

        // Release the player
        if (mediaPlayer != null) {
            mediaPlayer.stop();
            mIsSupposedToBePlaying = false;
            mediaPlayer.release();
            mediaPlayer = null;
        }

        //Unregister headset listener
        if(headsetReceiver != null) {
            unregisterReceiver(headsetReceiver);
        }
    }

    @Override
    public IBinder onBind(Intent arg0) {
        Log.d(this.getClass().getName(), "cancelShutdown from onBind");
        cancelShutdown();
        mServiceInUse = true;
        return mBinder;
    }

    @Override
    public boolean onUnbind(Intent arg0) {
        mServiceInUse = false;

        if (mIsSupposedToBePlaying ) {
            // Something is currently playing, or will be playing once
            // an in-progress action requesting audio focus ends, so don't stop
            // the service now.
            return true;

            // If there is a playlist but playback is paused, then wait a while
            // before stopping the service.
        } else if (currentSongStack!= null && currentSongStack.getCurrentSongsIDList().size() > 0) {
            Log.d(this.getClass().getName(), "scheduleDelayedShutdown from onUnbid");
            scheduleDelayedShutdown();
            return true;
        }
        stopSelf(mServiceStartId);
        return true;
    }

    @Override
    public void onRebind(final Intent intent) {
        Log.d(this.getClass().getName(), "cancelShutdown from onRebind");
        cancelShutdown();
        mServiceInUse = true;
    }

    public class MyBinder extends Binder {
        public MediaPlayerService getService() {
            return MediaPlayerService.this;
        }
    }

    public void registerListener(MediaPlayerServiceListener listener) {
        if(listener == null || currentListeners == null){
            stopSelf();
            return;
        }

        if(!currentListeners.contains(listener) ) {
            currentListeners.add(listener);
        }
    }
    
    public void unRegisterListener(MediaPlayerServiceListener listener) {
        if(listener == null || currentListeners == null){
            stopSelf();
            return;
        }
        currentListeners.remove(listener);
    }

    private void FetchLastPlayedSongs() {
        List<String> songsID = SoundBoxPreferences.LastSongs.getLastSongs();
        String lastSong = SoundBoxPreferences.LastPlayedSong.getLastPlayedSong();

        loadSongs(songsID, lastSong);
    }

    public void loadSongs(List<String> songsID, String currentSongID) {
        if (songsID.isEmpty()) {
            Log.d(TAG, "No songs to play");
            return;
        }
        this.songsIDList = new ArrayList<String>();
        this.songsIDList.addAll(songsID);

        int currentSongPosition;
        if (currentSongID.equals(BundleExtra.DefaultValues.DEFAULT_ID)) {
            currentSongPosition = 0;
        } else {
            currentSongPosition = this.songsIDList.indexOf(currentSongID);
            if(currentSongPosition == -1) {
                Log.d(TAG, "The first song to play is not in the loaded songs");
                return;
            }
        }

        // create the song stack
        currentSongStack = new SongStack(currentSongPosition, this.songsIDList, randomState);

        prepareMediaPlayer();
    }

    public Song getCurrentSong() {
        if (currentSongStack != null) {
            return currentSongStack.getCurrentSong();
        } else {
            return null;
        }
    }

    public boolean isPlaying() {
        return mIsSupposedToBePlaying;
    }

    public RandomState getRandomState() {
        return currentSongStack.getCurrentRandomState();
    }

    public RepeatState getRepeatState() {
        return repeatState;
    }

    public RandomState changeRandomState() {
        if (randomState == RandomState.Off) {
            randomState = RandomState.Shuffled;
        }
        else if (randomState == RandomState.Shuffled) {
            randomState = RandomState.Random;
        }
        else if (randomState == RandomState.Random) {
            randomState = RandomState.Off;
        }
        currentSongStack.setRandomState(randomState);
        fireListenersOnMediaPlayerStateChanged();
        return randomState;
    }

    public RepeatState changeRepeatState() {
        if (repeatState == RepeatState.Off) {
            repeatState = RepeatState.All;
        }
        else if (repeatState == RepeatState.All) {
            repeatState = RepeatState.One;
        }
        else if (repeatState == RepeatState.One) {
            repeatState = RepeatState.Off;
        }
        fireListenersOnMediaPlayerStateChanged();
        return repeatState;
    }

    public void seekTo(int position) {
        mediaPlayer.seekTo(position);
    }

    public int getCurrentPosition() {
        return mediaPlayer.getCurrentPosition();
    }

    public int getDuration() {
        return mediaPlayer.getDuration();
    }

    public void playAndPause() {
        if (!mediaPlayer.isPlaying()) {
            mediaPlayer.start();
            mIsSupposedToBePlaying = true;
            Log.d(this.getClass().getName(), "cancelShutdown from playAndPause");
            cancelShutdown();
        }
        else {
            mediaPlayer.pause();
            mIsSupposedToBePlaying = false;
            Log.d(this.getClass().getName(), "scheduleDelayedShutdown from playAndPause");
            scheduleDelayedShutdown();
        }
        fireListenersOnMediaPlayerStateChanged();
    }

    public void playNextSong() {
        try {
            currentSongStack.moveStackForward();
            // check if we started the playlist again
            if (currentSongStack.getCurrentSong().getID().equals(currentSongStack.getCurrentSongsIDList().get(0))) {
                if (repeatState == RepeatState.Off) {
                    // prepare the first song of the list, but do not play it.
                    mediaPlayer.stop();
                    Song currentSong = currentSongStack.getCurrentSong();
                    mediaPlayer.reset();
                    mediaPlayer.setDataSource(currentSong.getFile().getPath());
                    mediaPlayer.prepare();
                    Log.d(this.getClass().getName(), "scheduleDelayedShutdown from playNextSong");
                    scheduleDelayedShutdown();
                    mIsSupposedToBePlaying = false;
                    fireListenersOnMediaPlayerStateChanged();

                } else {
                    playCurrentSong();
                }
            } else {
                playCurrentSong();
            }
        } catch (Exception e) {
            fireListenersOnErrorRaised(e);
        }
    }

    public void playPreviousSong() {
        try {
            currentSongStack.moveStackBackward();
            playCurrentSong();
        } catch (Exception e) {
            fireListenersOnErrorRaised(e);
        }
    }

    public List<String> getSongsIDList() {
        if (currentSongStack.getCurrentRandomState() == RandomState.Random) {
            return songsIDList;
        } else {
            return currentSongStack.getCurrentSongsIDList();
        }
    }

    @Override
    public void onCompletion(MediaPlayer arg0) {
        try {
            if (repeatState == RepeatState.One) {
                playCurrentSong();
            } else {
                playNextSong();
                fireListenersOnMediaPlayerStateChanged();
            }
        }
        catch (Exception ex) {
            fireListenersOnErrorRaised(ex);
        }
    }

    private void playCurrentSong() {
        prepareMediaPlayer();
        mediaPlayer.start();
        mIsSupposedToBePlaying = true;
        Log.d(this.getClass().getName(), "cancelShutdown from playCurrentSong");
        cancelShutdown();
        fireListenersOnMediaPlayerStateChanged();
    }

    private void prepareMediaPlayer() {
        try {
            if (mediaPlayer.isPlaying()) {
                mediaPlayer.stop();
            }
            // play the song
            Song currentSong = currentSongStack.getCurrentSong();
            SoundBoxPreferences.LastPlayedSong.setLastPlayedSong(currentSong.getID());

            mediaPlayer.reset();
            mediaPlayer.setDataSource(currentSong.getFile().getPath());
            mediaPlayer.prepare();

            fireListenersOnMediaPlayerStateChanged();
        } catch (Exception e) {
            fireListenersOnErrorRaised(e);
        }
    }
    
    private void fireListenersOnMediaPlayerStateChanged() {
        if(currentListeners != null){
            for (MediaPlayerServiceListener listener : currentListeners) {
                listener.onMediaPlayerStateChanged();
            }
        }
        // if we are playing on foreground, update the notification
        if(isOnForeground) {
            Song currentSong = getCurrentSong();
            mNotification.updateNotification(currentSong.getArtist(), currentSong.getAlbum(), currentSong.getTitle(), mIsSupposedToBePlaying);
        }
    }

    private void fireListenersOnErrorRaised(Exception ex) {
        if(currentListeners == null){
            stopSelf();
            return;
        }
        for (MediaPlayerServiceListener listener : currentListeners) {
            listener.onExceptionRaised(ex);            
        }
        if(isOnForeground) {
            stopForeground(true);
        }
    }

    private void scheduleDelayedShutdown() {
        mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                SystemClock.elapsedRealtime() + IDLE_DELAY, mShutdownIntent);
        mShutdownScheduled = true;
    }

    private void cancelShutdown() {
        if (mShutdownScheduled) {
            mAlarmManager.cancel(mShutdownIntent);
            mShutdownScheduled = false;
        }
    }
}




Java Source Code List

com.arcusapp.soundbox.SoundBoxApplication.java
com.arcusapp.soundbox.activity.AboutActivity.java
com.arcusapp.soundbox.activity.FoldersActivity.java
com.arcusapp.soundbox.activity.MainActivity.java
com.arcusapp.soundbox.activity.SongsListActivity.java
com.arcusapp.soundbox.adapter.ArtistsActivityAdapter.java
com.arcusapp.soundbox.adapter.FoldersActivityAdapter.java
com.arcusapp.soundbox.adapter.PlaylistsActivityAdapter.java
com.arcusapp.soundbox.adapter.SongsListActivityAdapter.java
com.arcusapp.soundbox.adapter.ViewHolder.java
com.arcusapp.soundbox.data.MediaProvider.java
com.arcusapp.soundbox.data.SoundBoxPreferences.java
com.arcusapp.soundbox.fragment.ArtistsFragment.java
com.arcusapp.soundbox.fragment.PlayFragment.java
com.arcusapp.soundbox.fragment.PlaylistsFragment.java
com.arcusapp.soundbox.fragment.SongsListFragment.java
com.arcusapp.soundbox.model.AlbumEntry.java
com.arcusapp.soundbox.model.ArtistEntry.java
com.arcusapp.soundbox.model.BundleExtra.java
com.arcusapp.soundbox.model.Entry.java
com.arcusapp.soundbox.model.MediaEntry.java
com.arcusapp.soundbox.model.MediaPlayerServiceListener.java
com.arcusapp.soundbox.model.PlaylistEntry.java
com.arcusapp.soundbox.model.RandomState.java
com.arcusapp.soundbox.model.RepeatState.java
com.arcusapp.soundbox.model.SongEntry.java
com.arcusapp.soundbox.model.Song.java
com.arcusapp.soundbox.player.MediaPlayerNotification.java
com.arcusapp.soundbox.player.MediaPlayerService.java
com.arcusapp.soundbox.player.SongStack.java
com.arcusapp.soundbox.util.AudioBecomingNoisyHandler.java
com.arcusapp.soundbox.util.CustomViewPager.java
com.arcusapp.soundbox.util.DirectoryHelper.java
com.arcusapp.soundbox.util.MediaEntryHelper.java
com.arcusapp.soundbox.util.SlidingUpPanelLayout.java