package pasta.streamer; import android.app.PendingIntent; import android.app.Service; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.Color; import android.os.Build; import android.os.Handler; import android.os.IBinder; import android.support.annotation.Nullable; import android.support.v4.app.NotificationCompat; import android.support.v7.graphics.Palette; import android.util.Log; import com.bumptech.glide.Glide; import com.bumptech.glide.request.animation.GlideAnimation; import com.bumptech.glide.request.target.SimpleTarget; import com.spotify.sdk.android.player.Config; import com.spotify.sdk.android.player.ConnectionStateCallback; import com.spotify.sdk.android.player.PlayConfig; import com.spotify.sdk.android.player.Player; import com.spotify.sdk.android.player.PlayerNotificationCallback; import com.spotify.sdk.android.player.PlayerState; import com.spotify.sdk.android.player.PlayerStateCallback; import com.spotify.sdk.android.player.Spotify; import java.util.ArrayList; import java.util.List; import pasta.streamer.activities.PlayerActivity; import pasta.streamer.data.TrackListData; import pasta.streamer.utils.PreferenceUtils; public class PlayerService extends Service { public static final String ACTION_INIT = "pasta.ACTION_INIT", ACTION_PLAY = "pasta.ACTION_PLAY", ACTION_PLAY_EXTRA_START_POS = "pasta.ACTION_PLAY_EXTRA_START_POS", ACTION_PLAY_EXTRA_TRACKS = "pasta.ACTION_PLAY_EXTRA_TRACKS", ACTION_TOGGLE = "pasta.ACTION_TOGGLE", ACTION_NEXT = "pasta.ACTION_NEXT", ACTION_PREV = "pasta.ACTION_PREV", ACTION_MOVE_TRACK = "pasta.ACTION_MOVE_TRACK", ACTION_MOVE_TRACK_EXTRA_POS = "pasta.ACTION_MOVE_TRACK_EXTRA_POS", ACTION_MOVE_POS = "pasta.ACTION_MOVE_POS", ACTION_MOVE_POS_EXTRA_POS = "pasta.ACTION_MOVE_POS_EXTRA_POS", STATE_UPDATE = "pasta.STATE_UPDATE", EXTRA_TOKEN = "pasta.EXTRA_TOKEN", EXTRA_CLIENT_ID = "pasta.EXTRA_CLIENT_ID", EXTRA_PLAYING = "pasta.EXTRA_PLAYING", EXTRA_CUR_POSITION = "pasta.EXTRA_CUR_POSITION", EXTRA_CUR_TIME = "pasta.EXTRA_CUR_TIME", EXTRA_MAX_TIME = "pasta.EXTRA_MAX_TIME", EXTRA_CUR_TRACK = "pasta.EXTRA_SONG", EXTRA_TRACK_LIST = "pasta.EXTRA_TRACK_LIST"; public static final int UPDATE_INTERVAL = 500; private static final int NOTIFICATION_ID = 12345; private Player spotifyPlayer; private Config playerConfig; private PlayConfig spotifyPlayConfig; private PlayerState spotifyPlayerState; private ArrayList<TrackListData> trackList; private int curPos, errorCount; private boolean debugPlaying; private Pasta pasta; @Override public void onCreate() { super.onCreate(); pasta = (Pasta) getApplicationContext(); } @Override public void onDestroy() { super.onDestroy(); if (spotifyPlayer != null) spotifyPlayer.shutdownNow(); } private void initPlayer(String token, String clientId) { debugPlaying = false; if (playerConfig == null) { playerConfig = new Config(this, token, clientId); playerConfig.useCache(false); } spotifyPlayer = Spotify.getPlayer(playerConfig, this, new Player.InitializationObserver() { @Override public void onInitialized(Player player) { spotifyPlayer.setPlaybackBitrate(PreferenceUtils.getQuality(getApplicationContext())); spotifyPlayer.setRepeat(true); checkForState(); } @Override public void onError(Throwable throwable) { throwable.printStackTrace(); PlayerService.this.onError(throwable.getMessage()); } }); spotifyPlayer.addPlayerNotificationCallback(new PlayerNotificationCallback() { @Override public void onPlaybackEvent(EventType eventType, PlayerState playerState) { spotifyPlayerState = playerState; if (trackList != null) { if (!trackList.get(curPos).trackUri.matches(playerState.trackUri)) { if (trackList.get(getInfinitePos(curPos + 1)).trackUri.matches(playerState.trackUri)) curPos = getInfinitePos(curPos + 1); else { for (int i = 0; i < trackList.size(); i++) { if (trackList.get(i).trackUri.matches(playerState.trackUri)) { curPos = i; break; } } } } showNotification(); } } @Override public void onPlaybackError(ErrorType errorType, String s) { Log.e("PlayerService", errorType.name() + " " + s); onError(errorType.name() + " " + s); } }); spotifyPlayer.addConnectionStateCallback(new ConnectionStateCallback() { @Override public void onLoggedIn() { } @Override public void onLoggedOut() { } @Override public void onLoginFailed(Throwable throwable) { onError("Login Failed: " + throwable.getMessage()); } @Override public void onTemporaryError() { onError("Random error"); } @Override public void onConnectionMessage(String s) { } }); } private void onError(String message) { if (spotifyPlayer != null) { errorCount++; if (errorCount > 5 && errorCount < 20) { if (PreferenceUtils.isDebug(this)) pasta.showToast(message + ", attempting to restart..."); stopService(new Intent(this, PlayerService.class)); Intent intent = new Intent(PlayerService.ACTION_INIT); intent.setClass(this, PlayerService.class); intent.putExtra(PlayerService.EXTRA_TOKEN, playerConfig.oauthToken); intent.putExtra(PlayerService.EXTRA_CLIENT_ID, playerConfig.clientId); startService(intent); errorCount = 20; } else if (PreferenceUtils.isDebug(this)) pasta.showToast(message); } } @Override public int onStartCommand(Intent intent, int flags, int startId) { if (intent == null || intent.getAction() == null) { pasta.onError(this, "random start command"); return START_STICKY; } if (spotifyPlayer == null && !intent.getAction().matches(ACTION_INIT)) { pasta.onError(this, "null spotify player"); return START_STICKY; } switch (intent.getAction()) { case ACTION_INIT: initPlayer(intent.getStringExtra(EXTRA_TOKEN), intent.getStringExtra(EXTRA_CLIENT_ID)); break; case ACTION_PLAY: spotifyPlayer.pause(); trackList = intent.getParcelableArrayListExtra(ACTION_PLAY_EXTRA_TRACKS); List<String> trackUris = new ArrayList<>(); for (TrackListData trackListData : trackList) { trackUris.add(trackListData.trackUri); } curPos = getInfinitePos(intent.getIntExtra(ACTION_PLAY_EXTRA_START_POS, -1)); spotifyPlayConfig = PlayConfig.createFor(trackUris).withTrackIndex(curPos); spotifyPlayer.play(spotifyPlayConfig); debugPlaying = true; break; case ACTION_TOGGLE: if (spotifyPlayerState.playing) { spotifyPlayer.pause(); } else { spotifyPlayer.resume(); } debugPlaying = !debugPlaying; break; case ACTION_NEXT: spotifyPlayer.pause(); curPos = getInfinitePos(curPos + 1); spotifyPlayer.play(spotifyPlayConfig.withTrackIndex(curPos)); debugPlaying = true; break; case ACTION_PREV: spotifyPlayer.pause(); curPos = getInfinitePos(curPos - 1); spotifyPlayer.play(spotifyPlayConfig.withTrackIndex(curPos)); debugPlaying = true; break; case ACTION_MOVE_TRACK: spotifyPlayer.pause(); curPos = getInfinitePos(intent.getIntExtra(ACTION_MOVE_TRACK_EXTRA_POS, 0)); spotifyPlayer.play(spotifyPlayConfig.withTrackIndex(curPos)); debugPlaying = true; break; case ACTION_MOVE_POS: spotifyPlayer.seekToPosition(intent.getIntExtra(ACTION_MOVE_POS_EXTRA_POS, -1)); return START_STICKY; } return START_STICKY; } private int getInfinitePos(int pos) { if (pos >= trackList.size()) return 0; else if (pos < 0) return trackList.size() - 1; else return pos; } private void checkForState() { spotifyPlayer.getPlayerState(new PlayerStateCallback() { @Override public void onPlayerState(PlayerState playerState) { spotifyPlayerState = playerState; if (trackList != null && trackList.size() > 0) { sendUpdateToUI(); } if (debugPlaying && !playerState.playing && playerState.durationInMs == 0) { onError("Unknown error"); return; } new Handler().postDelayed(new Runnable() { @Override public void run() { if (!spotifyPlayer.isShutdown()) checkForState(); } }, UPDATE_INTERVAL); } }); } private void sendUpdateToUI() { TrackListData curTrack = trackList.get(curPos); Intent intent = new Intent(STATE_UPDATE); intent.putExtra(EXTRA_PLAYING, spotifyPlayerState.playing); intent.putExtra(EXTRA_CUR_POSITION, curPos); intent.putExtra(EXTRA_CUR_TIME, spotifyPlayerState.positionInMs); intent.putExtra(EXTRA_MAX_TIME, spotifyPlayerState.durationInMs); intent.putExtra(EXTRA_CUR_TRACK, curTrack); intent.putExtra(EXTRA_TRACK_LIST, trackList); sendBroadcast(intent); } private NotificationCompat.Builder getNotificationBuilder() { boolean vectors = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP; NotificationCompat.Builder builder = new NotificationCompat.Builder(PlayerService.this) .setSmallIcon(R.drawable.ic_notification) .setContentTitle(trackList.get(curPos).trackName) .addAction(vectors ? R.drawable.ic_prev : 0, "Previous", PendingIntent.getService(getApplicationContext(), 1, new Intent(getApplicationContext(), PlayerService.class).setAction(PlayerService.ACTION_PREV), PendingIntent.FLAG_UPDATE_CURRENT)) .addAction(vectors ? (spotifyPlayerState.playing ? R.drawable.ic_pause : R.drawable.ic_play) : 0, spotifyPlayerState.playing ? "Pause" : "Play", PendingIntent.getService(getApplicationContext(), 1, new Intent(getApplicationContext(), PlayerService.class).setAction(PlayerService.ACTION_TOGGLE), PendingIntent.FLAG_UPDATE_CURRENT)) .addAction(vectors ? R.drawable.ic_next : 0, "Next", PendingIntent.getService(getApplicationContext(), 1, new Intent(getApplicationContext(), PlayerService.class).setAction(PlayerService.ACTION_NEXT), PendingIntent.FLAG_UPDATE_CURRENT)) .setContentIntent(PendingIntent.getActivities(PlayerService.this, 0, new Intent[]{new Intent(PlayerService.this, PlayerActivity.class)}, 0)); if (trackList.get(curPos).artists.size() > 0) builder.setContentText(trackList.get(curPos).artists.get(0).artistName); return builder; } private void showNotification() { startForeground(NOTIFICATION_ID, getNotificationBuilder().build()); Glide.with(this).load(trackList.get(curPos).trackImage).asBitmap().into(new SimpleTarget<Bitmap>() { @Override public void onResourceReady(final Bitmap resource, GlideAnimation<? super Bitmap> glideAnimation) { Palette.from(resource).generate(new Palette.PaletteAsyncListener() { @Override public void onGenerated(Palette palette) { startForeground(NOTIFICATION_ID, getNotificationBuilder().setLargeIcon(resource).setColor(palette.getVibrantColor(Color.GRAY)).build()); } }); } }); } @Nullable @Override public IBinder onBind(Intent intent) { return null; } }