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;
+ }
+}