diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2ec2f457b6a55589c9149147b87ddfd41f2f6363..bd70e89a101841649b3abe1adf62bb9cf1727e17 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -19,6 +19,7 @@ + diff --git a/app/src/main/java/com/magnatune/eyecreate/companionformagnatune/service/PlayerService.java b/app/src/main/java/com/magnatune/eyecreate/companionformagnatune/service/PlayerService.java new file mode 100644 index 0000000000000000000000000000000000000000..d1a2c45de37eb561ec997ae114a001aaf85a77c8 --- /dev/null +++ b/app/src/main/java/com/magnatune/eyecreate/companionformagnatune/service/PlayerService.java @@ -0,0 +1,296 @@ +package com.magnatune.eyecreate.companionformagnatune.service; + +import android.app.Notification; +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; +import android.media.AudioManager; +import android.media.MediaPlayer; +import android.os.IBinder; +import android.support.annotation.Nullable; +import android.support.v4.app.NotificationManagerCompat; +import android.support.v4.content.LocalBroadcastManager; +import android.support.v4.media.MediaMetadataCompat; +import android.support.v4.media.session.MediaSessionCompat; +import android.support.v4.media.session.PlaybackStateCompat; +import android.support.v7.app.NotificationCompat; +import android.util.Log; + +import com.magnatune.eyecreate.companionformagnatune.model.Album; +import com.magnatune.eyecreate.companionformagnatune.model.MagnatuneDBManager; +import com.magnatune.eyecreate.companionformagnatune.model.Track; +import com.squareup.picasso.Picasso; +import com.squareup.picasso.Target; + +import java.io.IOException; + +import io.realm.Realm; + +public class PlayerService extends Service implements MediaPlayer.OnPreparedListener,MediaPlayer.OnCompletionListener,MediaPlayer.OnErrorListener,AudioManager.OnAudioFocusChangeListener{ + + public static String ACTION_TRACK_PLAY = "maganatune.playtrack"; + public static String ACTION_TRACK_PAUSE = "magnatune.pausetrack"; + public static String ACTION_TRACK_NEXT = "magnatune.next"; + public static String ACTION_TRACK_PREV = "magnatune.previous"; + public static String ACTION_TRACK_LIST = "magantune.gettracks"; + public static String ACTION_TRACK_QUEUE = "magnatune.queuetrack"; + public static String ACTION_TRACK_DEQUEUE = "magnatune.dequeuetrack"; + public static String ACTION_PLAYLIST_CLEAR = "magnatune.clear"; + + public static String EXTRA_TRACK_ID = "magnatune.trackid"; + public static String EXTRA_TRACK_PLAYLIST_POSITION = "magnatune.playlistposition"; + public static String EXTRA_PLAYLIST = "magnatune.playlist"; + + public static String ACTION_PLAYING_STARTED = "magnatune.startedplaying"; + + private static int notificationId = 58769423; + + MediaPlayer player; + boolean mediaPrepared = false; + Playlist currentPlaylist; + Notification displayedPlayData; + Realm db; + MediaSessionCompat session; + MediaSessionCompat.Callback mediaEventHandler = new MediaSessionCompat.Callback() { + @Override + public void onPlay() { + super.onPlay(); + if(mediaPrepared) { + player.start(); + } + } + + @Override + public void onPause() { + super.onPause(); + if(mediaPrepared && player.isPlaying()) { + player.pause(); + } + } + + @Override + public void onSkipToNext() { + super.onSkipToNext(); + playNextTrack(); + } + + @Override + public void onSkipToPrevious() { + super.onSkipToPrevious(); + playPreviousTrack(); + } + }; + + @Nullable + @Override + public IBinder onBind(Intent intent) { + return null; + } + + @Override + public void onCreate() { + super.onCreate(); + db = MagnatuneDBManager.getAlbumDB(); + player = new MediaPlayer(); + player.setOnPreparedListener(this); + player.setOnCompletionListener(this); + currentPlaylist = new Playlist(); + session = new MediaSessionCompat(this,"magnatune"); + session.setCallback(mediaEventHandler); + AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); + audioManager.requestAudioFocus(this,AudioManager.STREAM_MUSIC,AudioManager.AUDIOFOCUS_GAIN); + //TODO:work on possible media notification + // Compat.MediaStyle().setMediaSession(session.getSessionToken())).build(); + //NotificationManagerCompat.from(this).notify(notificationId,displayedPlayData); + } + + @Override + public void onDestroy() { + super.onDestroy(); + currentPlaylist.clearPlaylist(); + LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent(ACTION_PLAYLIST_CLEAR)); + NotificationManagerCompat.from(this).cancel(notificationId); + AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); + audioManager.abandonAudioFocus(this); + session.release(); + player.release(); + db.close(); + } + + @Override + public void onPrepared(MediaPlayer mediaPlayer) { + mediaPrepared = true; + mediaPlayer.start(); + session.setPlaybackState(new PlaybackStateCompat.Builder() + .setState(PlaybackStateCompat.STATE_PLAYING, player.getCurrentPosition(), 0) + .setActions(PlaybackStateCompat.ACTION_PAUSE | PlaybackStateCompat.ACTION_SKIP_TO_NEXT | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS) + .build()); + LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent(ACTION_PLAYING_STARTED)); + } + + private void playNextTrack() { + //If last track, trigger player stop + if(currentPlaylist.getPlaylistIds().size()==0) { + //Do nothing as there is nothing to play. + } else if(currentPlaylist.getPlaylistIds().indexOf(currentPlaylist.getCurrentlyPlayingId())+1>=currentPlaylist.getPlaylistIds().size()) { + stopSelf(); + } else { + Track nextTrack = db.where(Track.class).equalTo("isrc", currentPlaylist.getPlaylistIds().get(currentPlaylist.getPlaylistIds().indexOf(currentPlaylist.getCurrentlyPlayingId()) + 1)).findFirst(); + playThisTrack(nextTrack); + LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent(ACTION_TRACK_NEXT)); + } + } + + private void playPreviousTrack() { + //If first track, just seek back to start of track. + if(currentPlaylist.getPlaylistIds().size()==0) { + //Do nothing as there is nothing to play. + } else if(currentPlaylist.getPlaylistIds().indexOf(currentPlaylist.getCurrentlyPlayingId())==0 && mediaPrepared) { + player.seekTo(0); + LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent(ACTION_TRACK_PREV)); + } else { + Track nextTrack = db.where(Track.class).equalTo("isrc", currentPlaylist.getPlaylistIds().get(currentPlaylist.getPlaylistIds().indexOf(currentPlaylist.getCurrentlyPlayingId()) - 1)).findFirst(); + playThisTrack(nextTrack); + LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent(ACTION_TRACK_PREV)); + } + } + + private void playThisTrack(final Track trackToPlay) { + player.reset(); + mediaPrepared = false; + try { + player.setDataSource(trackToPlay.getUrl()); + player.prepareAsync(); + currentPlaylist.setCurrentlyPlayingId(trackToPlay.getIsrc()); + //Load resource and set upp media metadata + Picasso.with(this).load(db.where(Album.class).equalTo("albumname",trackToPlay.getAlbumname()).equalTo("artist.artistname",trackToPlay.getArtist()).findFirst().getCover_small()).into(new Target() { + @Override + public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) { + session.setMetadata(new MediaMetadataCompat.Builder() + .putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART,bitmap) + .putString(MediaMetadataCompat.METADATA_KEY_ALBUM,trackToPlay.getAlbumname()) + .putString(MediaMetadataCompat.METADATA_KEY_ARTIST,trackToPlay.getArtist()) + .putString(MediaMetadataCompat.METADATA_KEY_TITLE,trackToPlay.getTrackname()) + .putLong(MediaMetadataCompat.METADATA_KEY_DURATION,trackToPlay.getSeconds()*1000) + .build()); + } + + @Override + public void onBitmapFailed(Drawable errorDrawable) { + + } + + @Override + public void onPrepareLoad(Drawable placeHolderDrawable) { + + } + }); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + if(intent.getAction().equals(ACTION_TRACK_PLAY)) { + //Check if new track requested + if(intent.hasExtra(EXTRA_TRACK_PLAYLIST_POSITION) && currentPlaylist.getPlaylistIds().size()>intent.getIntExtra(EXTRA_TRACK_PLAYLIST_POSITION,-1)) { + Track nextTrack = db.where(Track.class).equalTo("isrc", currentPlaylist.getPlaylistIds().get(currentPlaylist.getPlaylistIds().indexOf(intent.getIntExtra(EXTRA_TRACK_PLAYLIST_POSITION, -1)))).findFirst(); + playThisTrack(nextTrack); + session.setPlaybackState(new PlaybackStateCompat.Builder() + .setState(PlaybackStateCompat.STATE_PLAYING,player.getCurrentPosition(),0) + .setActions(PlaybackStateCompat.ACTION_PAUSE|PlaybackStateCompat.ACTION_SKIP_TO_NEXT|PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS) + .build()); + LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent(ACTION_TRACK_PLAY)); + } else { + if (!mediaPrepared) { + //Do nothing as currently loading or stopped. + } else { + player.start(); + session.setPlaybackState(new PlaybackStateCompat.Builder() + .setState(PlaybackStateCompat.STATE_PLAYING,player.getCurrentPosition(),0) + .setActions(PlaybackStateCompat.ACTION_PAUSE|PlaybackStateCompat.ACTION_SKIP_TO_NEXT|PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS) + .build()); + LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent(ACTION_TRACK_PLAY)); + LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent(ACTION_PLAYING_STARTED)); + } + } + } else if(intent.getAction().equals(ACTION_TRACK_PAUSE)) { + if(player.isPlaying()) { + player.pause(); + session.setPlaybackState(new PlaybackStateCompat.Builder() + .setState(PlaybackStateCompat.STATE_PAUSED,player.getCurrentPosition(),0) + .setActions(PlaybackStateCompat.ACTION_PLAY|PlaybackStateCompat.ACTION_SKIP_TO_NEXT|PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS) + .build()); + LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent(ACTION_TRACK_PAUSE)); + } + } else if(intent.getAction().equals(ACTION_TRACK_NEXT)) { + playNextTrack(); + } else if(intent.getAction().equals(ACTION_TRACK_PREV)) { + playPreviousTrack(); + } else if(intent.getAction().equals(ACTION_TRACK_QUEUE)) { + if(intent.hasExtra(EXTRA_TRACK_ID)) { + currentPlaylist.addToPlaylist(intent.getStringExtra(EXTRA_TRACK_ID)); + Intent queue = new Intent(ACTION_TRACK_QUEUE); + queue.putExtra(EXTRA_TRACK_ID,intent.getStringExtra(EXTRA_TRACK_ID)); + LocalBroadcastManager.getInstance(this).sendBroadcast(queue); + } + } else if(intent.getAction().equals(ACTION_TRACK_DEQUEUE)) { + if(intent.hasExtra(EXTRA_TRACK_PLAYLIST_POSITION)) { + currentPlaylist.removeFromPlaylist(intent.getIntExtra(EXTRA_TRACK_PLAYLIST_POSITION, -1)); + Intent dequeue = new Intent(ACTION_TRACK_DEQUEUE); + dequeue.putExtra(EXTRA_TRACK_PLAYLIST_POSITION,intent.getIntExtra(EXTRA_TRACK_PLAYLIST_POSITION,-1)); + LocalBroadcastManager.getInstance(this).sendBroadcast(dequeue); + } + } else if(intent.getAction().equals(ACTION_PLAYLIST_CLEAR)) { + player.reset(); + mediaPrepared = false; + currentPlaylist.clearPlaylist(); + currentPlaylist.setCurrentPositionMill(0); + currentPlaylist.setCurrentlyPlayingId(null); + LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent(ACTION_PLAYLIST_CLEAR)); + } else if(intent.getAction().equals(ACTION_TRACK_LIST)) { + currentPlaylist.setCurrentPositionMill(player.getCurrentPosition()); + Intent trackData = new Intent(ACTION_TRACK_LIST); + trackData.putExtra(EXTRA_PLAYLIST,currentPlaylist); + LocalBroadcastManager.getInstance(this).sendBroadcast(trackData); + } + return START_STICKY; + } + + @Override + public void onCompletion(MediaPlayer mediaPlayer) { + playNextTrack(); + } + + @Override + public boolean onError(MediaPlayer mediaPlayer, int i, int i1) { + player.reset(); + mediaPrepared = false; + session.setPlaybackState(new PlaybackStateCompat.Builder().setState(PlaybackStateCompat.STATE_ERROR,player.getCurrentPosition(),0).setErrorMessage("Media error occured").build()); + Log.e("Magnatune","Media error occurred"); + return false; + } + + @Override + public void onAudioFocusChange(int i) { + switch (i) { + case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: + if(player.isPlaying()) player.setVolume(0.1f,0.1f); + break; + case AudioManager.AUDIOFOCUS_GAIN: + if(!player.isPlaying() && mediaPrepared) player.start(); + player.setVolume(1.0f,1.0f); + break; + case AudioManager.AUDIOFOCUS_LOSS: + stopSelf(); //TODO:might not want to outright kill + break; + case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: + if(player.isPlaying()) player.pause(); + break; + } + } + //TODO: look at implementing AUDIO_BECOMING_NOISY +} diff --git a/app/src/main/java/com/magnatune/eyecreate/companionformagnatune/service/Playlist.java b/app/src/main/java/com/magnatune/eyecreate/companionformagnatune/service/Playlist.java new file mode 100644 index 0000000000000000000000000000000000000000..d1a519c44debd975e8e824309d8c0ac363fcbb64 --- /dev/null +++ b/app/src/main/java/com/magnatune/eyecreate/companionformagnatune/service/Playlist.java @@ -0,0 +1,79 @@ +package com.magnatune.eyecreate.companionformagnatune.service; + +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.ArrayList; + +public class Playlist implements Parcelable { + + private ArrayList playlistIds; + private String currentlyPlayingId; + private int currentPositionMill; + + public Playlist() { + playlistIds = new ArrayList<>(); + } + + protected Playlist(Parcel in) { + in.readStringList(getPlaylistIds()); + setCurrentlyPlayingId(in.readString()); + setCurrentPositionMill(in.readInt()); + } + + public static final Creator CREATOR = new Creator() { + @Override + public Playlist createFromParcel(Parcel in) { + return new Playlist(in); + } + + @Override + public Playlist[] newArray(int size) { + return new Playlist[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int i) { + parcel.writeStringList(getPlaylistIds()); + parcel.writeString(getCurrentlyPlayingId()); + parcel.writeInt(getCurrentPositionMill()); + } + + public ArrayList getPlaylistIds() { + return playlistIds; + } + + public void addToPlaylist(String id) { + this.playlistIds.add(id); + } + + public void removeFromPlaylist(int itemPosition) { + this.playlistIds.remove(itemPosition); + } + + public void clearPlaylist() { + this.playlistIds.clear(); + } + + public String getCurrentlyPlayingId() { + return currentlyPlayingId; + } + + public void setCurrentlyPlayingId(String currentlyPlayingId) { + this.currentlyPlayingId = currentlyPlayingId; + } + + public int getCurrentPositionMill() { + return currentPositionMill; + } + + public void setCurrentPositionMill(int currentPositionMill) { + this.currentPositionMill = currentPositionMill; + } +}