com.scooter1556.sms.android.activity.FullScreenPlayerActivity.java Source code

Java tutorial

Introduction

Here is the source code for com.scooter1556.sms.android.activity.FullScreenPlayerActivity.java

Source

/*
 * Author: Scott Ware <scoot.software@gmail.com>
 * Copyright (c) 2015 Scott Ware
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package com.scooter1556.sms.android.activity;

import android.content.ComponentName;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
import android.os.SystemClock;
import android.support.annotation.NonNull;
import android.support.v4.content.ContextCompat;
import android.support.v4.media.MediaBrowserCompat;
import android.support.v4.media.MediaDescriptionCompat;
import android.support.v4.media.MediaMetadataCompat;
import android.support.v4.media.session.MediaControllerCompat;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat;
import android.text.format.DateUtils;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.SeekBar;
import android.widget.TextView;

import com.bumptech.glide.Glide;
import com.bumptech.glide.request.RequestOptions;
import com.scooter1556.sms.android.R;
import com.scooter1556.sms.android.service.MediaService;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;

/**
 * A full screen player that shows the current playing music with a background image
 * depicting the album art. The activity also has controls to seek/pause/play media.
 */
public class FullScreenPlayerActivity extends ActionBarCastActivity {
    private static final String TAG = "FSPlayerActivity";

    private static final long PROGRESS_UPDATE_INTERNAL = 1000;
    private static final long PROGRESS_UPDATE_INITIAL_INTERVAL = 100;

    private ImageView skipPrev;
    private ImageView skipNext;
    private ImageView playPause;
    private ImageView shuffle;
    private ImageView repeat;
    private TextView start;
    private TextView end;
    private SeekBar seekbar;
    private TextView title;
    private TextView subtitle;
    private TextView extra;
    private ProgressBar loading;
    private View controllers;
    private Drawable pauseDrawable;
    private Drawable playDrawable;
    private ImageView backgroundImage;

    private static int shuffleMode = PlaybackStateCompat.SHUFFLE_MODE_NONE;
    private static int repeatMode = PlaybackStateCompat.REPEAT_MODE_NONE;

    private final Handler handler = new Handler();
    private MediaBrowserCompat mediaBrowser;

    private final Runnable updateProgressTask = new Runnable() {
        @Override
        public void run() {
            updateProgress();
        }
    };

    private final ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();

    private ScheduledFuture<?> scheduleFuture;
    private PlaybackStateCompat lastPlaybackState;

    private static MediaControllerCompat mediaController;

    private final MediaControllerCompat.Callback callback = new MediaControllerCompat.Callback() {
        @Override
        public void onPlaybackStateChanged(@NonNull PlaybackStateCompat state) {
            updatePlaybackState(state);
        }

        @Override
        public void onMetadataChanged(MediaMetadataCompat metadata) {
            if (metadata != null) {
                updateMediaDescription(metadata.getDescription());
                updateDuration(metadata);
            }
        }
    };

    private final MediaBrowserCompat.ConnectionCallback connectionCallback = new MediaBrowserCompat.ConnectionCallback() {
        @Override
        public void onConnected() {
            try {
                connectToSession(mediaBrowser.getSessionToken());
            } catch (RemoteException e) {
                Log.e(TAG, "Could not connect media controller", e);
            }
        }
    };

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_full_screen_player);
        initialiseToolbar();
        if (getSupportActionBar() != null) {
            getSupportActionBar().setDisplayHomeAsUpEnabled(true);
            getSupportActionBar().setTitle("");
        }

        backgroundImage = (ImageView) findViewById(R.id.background_image);
        pauseDrawable = ContextCompat.getDrawable(this, R.drawable.ic_pause_white_48dp);
        playDrawable = ContextCompat.getDrawable(this, R.drawable.ic_play_arrow_white_48dp);
        playPause = (ImageView) findViewById(R.id.play_pause);
        skipNext = (ImageView) findViewById(R.id.next);
        skipPrev = (ImageView) findViewById(R.id.prev);
        shuffle = (ImageView) findViewById(R.id.shuffle);
        repeat = (ImageView) findViewById(R.id.repeat);
        start = (TextView) findViewById(R.id.startText);
        end = (TextView) findViewById(R.id.endText);
        seekbar = (SeekBar) findViewById(R.id.seekBar);
        title = (TextView) findViewById(R.id.title);
        subtitle = (TextView) findViewById(R.id.subtitle);
        extra = (TextView) findViewById(R.id.extra);
        loading = (ProgressBar) findViewById(R.id.progressBar);
        controllers = findViewById(R.id.controllers);

        skipNext.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                MediaControllerCompat.TransportControls controls = mediaController.getTransportControls();
                controls.skipToNext();
            }
        });

        skipPrev.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                MediaControllerCompat.TransportControls controls = mediaController.getTransportControls();
                controls.skipToPrevious();
            }
        });

        playPause.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                PlaybackStateCompat state = mediaController.getPlaybackState();
                if (state != null) {
                    MediaControllerCompat.TransportControls controls = mediaController.getTransportControls();
                    switch (state.getState()) {
                    case PlaybackStateCompat.STATE_PLAYING: // fall through
                    case PlaybackStateCompat.STATE_BUFFERING:
                        controls.pause();
                        stopSeekbarUpdate();
                        break;
                    case PlaybackStateCompat.STATE_PAUSED:
                    case PlaybackStateCompat.STATE_STOPPED:
                        controls.play();
                        scheduleSeekbarUpdate();
                        break;
                    }
                }
            }
        });

        shuffle.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                PlaybackStateCompat state = mediaController.getPlaybackState();
                MediaControllerCompat.TransportControls controls = mediaController.getTransportControls();

                if (state == null) {
                    return;
                }

                for (PlaybackStateCompat.CustomAction action : state.getCustomActions()) {
                    switch (action.getAction()) {
                    case MediaService.STATE_SHUFFLE_ON:
                        controls.sendCustomAction(MediaService.STATE_SHUFFLE_ON, null);
                        break;

                    case MediaService.STATE_SHUFFLE_OFF:
                        controls.sendCustomAction(MediaService.STATE_SHUFFLE_OFF, null);
                        break;
                    }
                }

                updatePlaybackState(state);
            }
        });

        repeat.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                PlaybackStateCompat state = mediaController.getPlaybackState();
                MediaControllerCompat.TransportControls controls = mediaController.getTransportControls();

                if (state == null) {
                    return;
                }

                for (PlaybackStateCompat.CustomAction action : state.getCustomActions()) {
                    switch (action.getAction()) {
                    case MediaService.STATE_REPEAT_NONE:
                    case MediaService.STATE_REPEAT_ALL:
                    case MediaService.STATE_REPEAT_ONE:
                        controls.sendCustomAction(action.getAction(), null);
                        break;
                    }
                }

                updatePlaybackState(state);
            }
        });

        seekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                start.setText(DateUtils.formatElapsedTime(progress / 1000));
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {
                stopSeekbarUpdate();
            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {
                mediaController.getTransportControls().seekTo(seekBar.getProgress());
                scheduleSeekbarUpdate();
            }
        });

        // Only update from the intent if we are not recreating from a config change:
        if (savedInstanceState == null) {
            updateFromParams(getIntent());
        }

        mediaBrowser = new MediaBrowserCompat(this, new ComponentName(this, MediaService.class), connectionCallback,
                null);
    }

    private void connectToSession(MediaSessionCompat.Token token) throws RemoteException {
        mediaController = new MediaControllerCompat(FullScreenPlayerActivity.this, token);

        if (mediaController.getMetadata() == null) {
            finish();
            return;
        }

        MediaControllerCompat.setMediaController(this, mediaController);
        mediaController.registerCallback(callback);
        PlaybackStateCompat state = mediaController.getPlaybackState();
        updatePlaybackState(state);
        MediaMetadataCompat metadata = mediaController.getMetadata();

        if (metadata != null) {
            updateMediaDescription(metadata.getDescription());
            updateDuration(metadata);
        }

        updateProgress();

        if (state != null && (state.getState() == PlaybackStateCompat.STATE_PLAYING
                || state.getState() == PlaybackStateCompat.STATE_BUFFERING)) {
            scheduleSeekbarUpdate();
        }
    }

    private void updateFromParams(Intent intent) {
        if (intent != null) {
            MediaDescriptionCompat description = intent
                    .getParcelableExtra(HomeActivity.EXTRA_CURRENT_MEDIA_DESCRIPTION);

            if (description != null) {
                updateMediaDescription(description);
            }
        }
    }

    private void scheduleSeekbarUpdate() {
        stopSeekbarUpdate();
        if (!executorService.isShutdown()) {
            scheduleFuture = executorService.scheduleAtFixedRate(new Runnable() {
                @Override
                public void run() {
                    handler.post(updateProgressTask);
                }
            }, PROGRESS_UPDATE_INITIAL_INTERVAL, PROGRESS_UPDATE_INTERNAL, TimeUnit.MILLISECONDS);
        }
    }

    private void stopSeekbarUpdate() {
        if (scheduleFuture != null) {
            scheduleFuture.cancel(false);
        }
    }

    @Override
    public void onStart() {
        super.onStart();
        if (mediaBrowser != null) {
            mediaBrowser.connect();
        }
    }

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

        if (mediaBrowser != null) {
            mediaBrowser.disconnect();
        }

        if (mediaController != null) {
            mediaController.unregisterCallback(callback);
        }
    }

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

        stopSeekbarUpdate();
        executorService.shutdown();
    }

    private void updateMediaDescription(MediaDescriptionCompat description) {
        if (description == null) {
            return;
        }

        title.setText(description.getTitle());
        subtitle.setText(description.getSubtitle());

        // Cover Art
        RequestOptions options = new RequestOptions().fallback(R.drawable.ic_not_interested_black_48dp);

        Glide.with(this).load(description.getIconUri().toString()).apply(options).into(backgroundImage);
    }

    private void updateDuration(MediaMetadataCompat metadata) {
        if (metadata == null) {
            return;
        }

        int duration = (int) metadata.getLong(MediaMetadataCompat.METADATA_KEY_DURATION);
        seekbar.setMax(duration);
        end.setText(DateUtils.formatElapsedTime(duration / 1000));
    }

    private void updatePlaybackState(PlaybackStateCompat state) {
        if (state == null) {
            return;
        }

        lastPlaybackState = state;
        if (mediaController != null && mediaController.getExtras() != null) {
            String castName = mediaController.getExtras().getString(MediaService.EXTRA_CONNECTED_CAST);
            String extraInfo = castName == null ? "" : getResources().getString(R.string.cast_to_device, castName);
            extra.setText(extraInfo);
        }

        switch (state.getState()) {

        case PlaybackStateCompat.STATE_PLAYING:
            loading.setVisibility(INVISIBLE);
            playPause.setVisibility(VISIBLE);
            playPause.setImageDrawable(pauseDrawable);
            controllers.setVisibility(VISIBLE);
            scheduleSeekbarUpdate();
            break;

        case PlaybackStateCompat.STATE_PAUSED:
            controllers.setVisibility(VISIBLE);
            loading.setVisibility(INVISIBLE);
            playPause.setVisibility(VISIBLE);
            playPause.setImageDrawable(playDrawable);
            stopSeekbarUpdate();
            break;

        case PlaybackStateCompat.STATE_NONE:

        case PlaybackStateCompat.STATE_STOPPED:
            loading.setVisibility(INVISIBLE);
            playPause.setVisibility(VISIBLE);
            playPause.setImageDrawable(playDrawable);
            stopSeekbarUpdate();
            break;

        case PlaybackStateCompat.STATE_BUFFERING:
            playPause.setVisibility(INVISIBLE);
            loading.setVisibility(VISIBLE);
            extra.setText(R.string.state_loading);
            stopSeekbarUpdate();
            break;

        default:
            Log.d(TAG, "Unhandled state: " + state.getState());
        }

        skipNext.setVisibility(
                (state.getActions() & PlaybackStateCompat.ACTION_SKIP_TO_NEXT) == 0 ? INVISIBLE : VISIBLE);
        skipPrev.setVisibility(
                (state.getActions() & PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS) == 0 ? INVISIBLE : VISIBLE);
        shuffle.setVisibility(
                (state.getActions() & PlaybackStateCompat.ACTION_SET_SHUFFLE_MODE) == 0 ? INVISIBLE : VISIBLE);
        repeat.setVisibility(
                (state.getActions() & PlaybackStateCompat.ACTION_SET_REPEAT_MODE) == 0 ? INVISIBLE : VISIBLE);

        // Custom Actions
        for (PlaybackStateCompat.CustomAction action : state.getCustomActions()) {
            switch (action.getAction()) {
            case MediaService.STATE_SHUFFLE_ON:
                shuffle.setColorFilter(ContextCompat.getColor(getBaseContext(), R.color.accent));
                break;

            case MediaService.STATE_SHUFFLE_OFF:
                shuffle.clearColorFilter();
                break;

            case MediaService.STATE_REPEAT_NONE:
                repeat.setImageResource(R.drawable.ic_repeat_white_24dp);
                repeat.clearColorFilter();
                break;

            case MediaService.STATE_REPEAT_ALL:
                repeat.setImageResource(R.drawable.ic_repeat_white_24dp);
                repeat.setColorFilter(ContextCompat.getColor(getBaseContext(), R.color.accent));
                break;

            case MediaService.STATE_REPEAT_ONE:
                repeat.setImageResource(R.drawable.ic_repeat_one_white_24dp);
                repeat.setColorFilter(ContextCompat.getColor(getBaseContext(), R.color.accent));
                break;
            }
        }
    }

    private void updateProgress() {
        if (lastPlaybackState == null) {
            return;
        }

        long currentPosition = lastPlaybackState.getPosition();

        if (lastPlaybackState.getState() != PlaybackStateCompat.STATE_PAUSED) {
            // Calculate the elapsed time between the last position update and now and unless
            // paused, we can assume (delta * speed) + current position is approximately the
            // latest position. This ensures that we do not repeatedly call the getPlaybackState()
            // on MediaControllerCompat.
            long timeDelta = SystemClock.elapsedRealtime() - lastPlaybackState.getLastPositionUpdateTime();
            currentPosition += (int) timeDelta * lastPlaybackState.getPlaybackSpeed();
        }

        seekbar.setProgress((int) currentPosition);
    }
}