net.simno.klingar.ui.PlayerController.java Source code

Java tutorial

Introduction

Here is the source code for net.simno.klingar.ui.PlayerController.java

Source

/*
 * Copyright (C) 2017 Simon Norberg
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.simno.klingar.ui;

import android.animation.ObjectAnimator;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.media.session.PlaybackStateCompat;
import android.support.v4.media.session.PlaybackStateCompat.State;
import android.support.v4.view.MenuItemCompat;
import android.support.v4.widget.ContentLoadingProgressBar;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.format.DateUtils;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewAnimationUtils;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.SeekBar;
import android.widget.TextView;

import com.bumptech.glide.Glide;

import net.simno.klingar.KlingarApp;
import net.simno.klingar.R;
import net.simno.klingar.data.model.Track;
import net.simno.klingar.playback.MusicController;
import net.simno.klingar.playback.QueueManager;
import net.simno.klingar.playback.QueueManager.RepeatMode;
import net.simno.klingar.playback.QueueManager.ShuffleMode;
import net.simno.klingar.ui.adapter.QueueAdapter;
import net.simno.klingar.ui.widget.DividerItemDecoration;
import net.simno.klingar.util.Rx;

import javax.inject.Inject;

import butterknife.BindDrawable;
import butterknife.BindString;
import butterknife.BindView;
import butterknife.OnClick;

import static com.bluelinelabs.conductor.rxlifecycle2.ControllerEvent.DETACH;
import static net.simno.klingar.ui.ToolbarOwner.TITLE_GONE;

public class PlayerController extends BaseController implements QueueAdapter.OnTrackClickListener {

    private static final int[] PLAY = { -R.attr.state_pause };
    private static final int[] PAUSE = { R.attr.state_pause };
    private static final int[] SHUFFLE_OFF = { -R.attr.state_shuffle_all };
    private static final int[] SHUFFLE_ALL = { R.attr.state_shuffle_all };
    private static final int[] REPEAT_OFF = { -R.attr.state_repeat_all, -R.attr.state_repeat_one };
    private static final int[] REPEAT_ALL = { R.attr.state_repeat_all, -R.attr.state_repeat_one };
    private static final int[] REPEAT_ONE = { -R.attr.state_repeat_all, R.attr.state_repeat_one };
    private static final int[] QUEUE = { -R.attr.state_track };
    private static final int[] TRACK = { R.attr.state_track };
    private final QueueAdapter queueAdapter;
    @BindView(R.id.content_loading)
    ContentLoadingProgressBar contentLoading;
    @BindView(R.id.player_background_image)
    ImageView background;
    @BindView(R.id.player_queue)
    RecyclerView queueRecyclerView;
    @BindView(R.id.player_track_title)
    TextView trackTitle;
    @BindView(R.id.player_artist_title)
    TextView artistTitle;
    @BindView(R.id.player_seekbar)
    SeekBar seekBar;
    @BindView(R.id.player_elapsed_time)
    TextView elapsedTime;
    @BindView(R.id.player_total_time)
    TextView totalTime;
    @BindView(R.id.player_shuffle)
    ImageView shuffleButton;
    @BindView(R.id.player_repeat)
    ImageView repeatButton;
    @BindView(R.id.player_play_pause)
    ImageView playPauseButton;
    @BindDrawable(R.drawable.item_divider)
    Drawable itemDivider;
    @BindString(R.string.description_play)
    String descPlay;
    @BindString(R.string.description_pause)
    String descPause;
    @BindString(R.string.description_shuffle_off)
    String descShuffleOff;
    @BindString(R.string.description_shuffle_all)
    String descShuffleAll;
    @BindString(R.string.description_repeat_off)
    String descRepeatOff;
    @BindString(R.string.description_repeat_all)
    String descRepeatAll;
    @BindString(R.string.description_repeat_one)
    String descRepeatOne;
    @BindString(R.string.description_queue)
    String descQueue;
    @BindString(R.string.description_track)
    String descTrack;
    @Inject
    ToolbarOwner toolbarOwner;
    @Inject
    QueueManager queueManager;
    @Inject
    MusicController musicController;
    @Inject
    Rx rx;
    private boolean isSeeking;
    private boolean isQueueVisible;

    public PlayerController(Bundle args) {
        super(args);
        queueAdapter = new QueueAdapter(this);
    }

    @Override
    protected int getLayoutResource() {
        return R.layout.controller_player;
    }

    @Override
    protected void injectDependencies() {
        if (getActivity() != null) {
            KlingarApp.get(getActivity()).component().inject(this);
        }
    }

    @NonNull
    @Override
    protected View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
        View view = super.onCreateView(inflater, container);

        toolbarOwner.setConfig(ToolbarOwner.Config.builder().background(false).backNavigation(true)
                .titleAlpha(TITLE_GONE).build());

        setHasOptionsMenu(true);

        queueRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
        queueRecyclerView.setHasFixedSize(true);
        queueRecyclerView.addItemDecoration(new DividerItemDecoration(itemDivider));

        queueRecyclerView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
        if (isQueueVisible) {
            queueRecyclerView.animate().alpha(1).setDuration(0).withLayer();
            background.setImageAlpha(0);
        } else {
            queueRecyclerView.animate().alpha(0).setDuration(0).withLayer();
            background.setImageAlpha(255);
        }

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

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {
                isSeeking = true;
            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {
                musicController.seekTo(seekBar.getProgress() * 1000);
                isSeeking = false;
            }
        });

        return view;
    }

    @Override
    protected void onAttach(@NonNull View view) {
        super.onAttach(view);
        queueRecyclerView.setAdapter(queueAdapter);
        observePlaybackState();
    }

    @Override
    protected void onDetach(@NonNull View view) {
        super.onDetach(view);
        queueRecyclerView.setAdapter(null);
    }

    @Override
    public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
        inflater.inflate(R.menu.menu_player, menu);
        super.onCreateOptionsMenu(menu, inflater);
    }

    @Override
    public void onPrepareOptionsMenu(@NonNull Menu menu) {
        MenuItem item = menu.findItem(R.id.action_queue_track);
        ImageView actionView = (ImageView) MenuItemCompat.getActionView(item);
        actionView.setImageState(isQueueVisible ? TRACK : QUEUE, true);
        actionView.setContentDescription(isQueueVisible ? descTrack : descQueue);
        actionView.setOnClickListener(view -> {
            actionView.setImageState(isQueueVisible ? QUEUE : TRACK, true);
            actionView.setContentDescription(isQueueVisible ? descQueue : descTrack);
            toggleQueue();
        });
    }

    private void toggleQueue() {
        if (isQueueVisible) {
            queueRecyclerView.animate().alpha(0).setDuration(200).withLayer();
            background.setImageAlpha(255);
            int width = background.getWidth();
            ViewAnimationUtils.createCircularReveal(background, width, 0, 100, width).start();
        } else {
            ObjectAnimator.ofInt(background, "imageAlpha", 0).setDuration(200).start();
            queueRecyclerView.animate().alpha(1).setDuration(0).withLayer();
            int height = queueRecyclerView.getHeight();
            ViewAnimationUtils.createCircularReveal(queueRecyclerView, 0, height, 100, height).start();
        }
        isQueueVisible = !isQueueVisible;
    }

    @OnClick(R.id.player_shuffle)
    void onClickShuffle() {
        musicController.shuffle();
    }

    @OnClick(R.id.player_repeat)
    void onClickRepeat() {
        musicController.repeat();
    }

    @OnClick(R.id.player_play_pause)
    void onClickPlayPause() {
        musicController.playPause();
    }

    @OnClick(R.id.player_next)
    void onClickNext(ImageView nextButton) {
        ((Animatable) nextButton.getDrawable()).start();
        musicController.next();
    }

    @OnClick(R.id.player_previous)
    void onClickPrevious(ImageView previousButton) {
        ((Animatable) previousButton.getDrawable()).start();
        musicController.previous();
    }

    private void observePlaybackState() {
        disposables.add(musicController.progress().compose(bindUntilEvent(DETACH)).compose(rx.flowableSchedulers())
                .subscribe(progress -> {
                    if (!isSeeking) {
                        seekBar.setProgress((int) (progress / 1000));
                    }
                }, Rx::onError));

        disposables.add(musicController.state().compose(bindUntilEvent(DETACH)).compose(rx.flowableSchedulers())
                .subscribe(this::updatePlayButton, Rx::onError));

        disposables.add(queueManager.mode().compose(bindUntilEvent(DETACH)).compose(rx.flowableSchedulers())
                .subscribe(pair -> {
                    updateShuffleButton(pair.first);
                    updateRepeatButton(pair.second);
                }, Rx::onError));

        disposables.add(queueManager.queue().compose(bindUntilEvent(DETACH)).compose(rx.flowableSchedulers())
                .subscribe(pair -> {
                    queueAdapter.setQueue(pair.first, pair.second);
                    updateTrackInfo(pair.first.get(pair.second));
                }, Rx::onError));
    }

    private void updatePlayButton(@State int state) {
        if (state == PlaybackStateCompat.STATE_PLAYING || state == PlaybackStateCompat.STATE_BUFFERING) {
            playPauseButton.setImageState(PAUSE, true);
            playPauseButton.setContentDescription(descPause);
        } else {
            playPauseButton.setImageState(PLAY, true);
            playPauseButton.setContentDescription(descPlay);
        }
    }

    private void updateShuffleButton(@ShuffleMode int shuffleMode) {
        if (shuffleMode == QueueManager.SHUFFLE_OFF) {
            shuffleButton.setImageState(SHUFFLE_OFF, true);
            shuffleButton.setContentDescription(descShuffleOff);
        } else if (shuffleMode == QueueManager.SHUFFLE_ALL) {
            shuffleButton.setImageState(SHUFFLE_ALL, true);
            shuffleButton.setContentDescription(descShuffleAll);
        }
    }

    private void updateRepeatButton(@RepeatMode int repeatMode) {
        if (repeatMode == QueueManager.REPEAT_OFF) {
            repeatButton.setImageState(REPEAT_OFF, true);
            repeatButton.setContentDescription(descRepeatOff);
        } else if (repeatMode == QueueManager.REPEAT_ALL) {
            repeatButton.setImageState(REPEAT_ALL, true);
            repeatButton.setContentDescription(descRepeatAll);
        } else if (repeatMode == QueueManager.REPEAT_ONE) {
            repeatButton.setImageState(REPEAT_ONE, true);
            repeatButton.setContentDescription(descRepeatOne);
        }
    }

    private void updateTrackInfo(@NonNull Track track) {
        contentLoading.hide();
        Glide.with(getActivity()).load(track.thumb()).crossFade().into(background);

        seekBar.setMax((int) track.duration() / 1000);
        totalTime.setText(DateUtils.formatElapsedTime(track.duration() / 1000));

        trackTitle.setText(track.title());
        artistTitle.setText(track.artistTitle());
    }

    @Override
    public void onTrackClicked(Track track) {
        musicController.playQueueItem(track.queueItemId());
    }
}