package com.devbrackets.android.exomediademo.ui.activity; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.v7.app.AppCompatActivity; import android.view.View; import android.widget.ImageButton; 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.RequestManager; import com.devbrackets.android.exomedia.util.TimeFormatUtil; import com.devbrackets.android.exomediademo.App; import com.devbrackets.android.exomediademo.R; import com.devbrackets.android.exomediademo.data.MediaItem; import com.devbrackets.android.exomediademo.data.Samples; import com.devbrackets.android.exomediademo.manager.PlaylistManager; import com.devbrackets.android.playlistcore.event.MediaProgress; import com.devbrackets.android.playlistcore.event.PlaylistItemChange; import com.devbrackets.android.playlistcore.listener.PlaylistListener; import com.devbrackets.android.playlistcore.listener.ProgressListener; import com.devbrackets.android.playlistcore.service.PlaylistServiceCore; import java.util.LinkedList; import java.util.List; /** * An example activity to show how to implement and audio UI * that interacts with the {@link com.devbrackets.android.playlistcore.service.BasePlaylistService} * and {@link com.devbrackets.android.playlistcore.manager.BasePlaylistManager} classes. */ public class AudioPlayerActivity extends AppCompatActivity implements PlaylistListener<MediaItem>, ProgressListener { public static final String EXTRA_INDEX = "EXTRA_INDEX"; public static final int PLAYLIST_ID = 4; //Arbitrary, for the example private ProgressBar loadingBar; private ImageView artworkView; private TextView currentPositionView; private TextView durationView; private SeekBar seekBar; private boolean shouldSetDuration; private boolean userInteracting; private ImageButton previousButton; private ImageButton playPauseButton; private ImageButton nextButton; private PlaylistManager playlistManager; private int selectedIndex = 0; private RequestManager glide; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.audio_player_activity); retrieveExtras(); init(); } @Override protected void onPause() { super.onPause(); playlistManager.unRegisterPlaylistListener(this); playlistManager.unRegisterProgressListener(this); } @Override protected void onResume() { super.onResume(); playlistManager = App.getPlaylistManager(); playlistManager.registerPlaylistListener(this); playlistManager.registerProgressListener(this); //Makes sure to retrieve the current playback information updateCurrentPlaybackInformation(); } @Override public boolean onPlaylistItemChanged(MediaItem currentItem, boolean hasNext, boolean hasPrevious) { shouldSetDuration = true; //Updates the button states nextButton.setEnabled(hasNext); previousButton.setEnabled(hasPrevious); //Loads the new image glide.load(currentItem.getArtworkUrl()).into(artworkView); return true; } @Override public boolean onPlaybackStateChanged(@NonNull PlaylistServiceCore.PlaybackState playbackState) { switch (playbackState) { case STOPPED: finish(); break; case RETRIEVING: case PREPARING: case SEEKING: restartLoading(); break; case PLAYING: doneLoading(true); break; case PAUSED: doneLoading(false); break; default: break; } return true; } @Override public boolean onProgressUpdated(@NonNull MediaProgress progress) { if (shouldSetDuration && progress.getDuration() > 0) { shouldSetDuration = false; setDuration(progress.getDuration()); } if (!userInteracting) { seekBar.setSecondaryProgress((int) (progress.getDuration() * progress.getBufferPercentFloat())); seekBar.setProgress((int)progress.getPosition()); currentPositionView.setText(TimeFormatUtil.formatMs(progress.getPosition())); } return true; } /** * Makes sure to update the UI to the current playback item. */ private void updateCurrentPlaybackInformation() { PlaylistItemChange<MediaItem> itemChangedEvent = playlistManager.getCurrentItemChange(); if (itemChangedEvent != null) { onPlaylistItemChanged(itemChangedEvent.getCurrentItem(), itemChangedEvent.hasNext(), itemChangedEvent.hasPrevious()); } PlaylistServiceCore.PlaybackState currentPlaybackState = playlistManager.getCurrentPlaybackState(); if (currentPlaybackState != PlaylistServiceCore.PlaybackState.STOPPED) { onPlaybackStateChanged(currentPlaybackState); } MediaProgress progressEvent = playlistManager.getCurrentProgress(); if (progressEvent != null) { onProgressUpdated(progressEvent); } } /** * Retrieves the extra associated with the selected playlist index * so that we can start playing the correct item. */ private void retrieveExtras() { Bundle extras = getIntent().getExtras(); selectedIndex = extras.getInt(EXTRA_INDEX, 0); } /** * Performs the initialization of the views and any other * general setup */ private void init() { retrieveViews(); setupListeners(); glide = Glide.with(getApplicationContext()); boolean generatedPlaylist = setupPlaylistManager(); startPlayback(generatedPlaylist); } /** * Called when we receive a notification that the current item is * done loading. This will then update the view visibilities and * states accordingly. * * @param isPlaying True if the audio item is currently playing */ private void doneLoading(boolean isPlaying) { loadCompleted(); updatePlayPauseImage(isPlaying); } /** * Updates the Play/Pause image to represent the correct playback state * * @param isPlaying True if the audio item is currently playing */ private void updatePlayPauseImage(boolean isPlaying) { int resId = isPlaying ? R.drawable.playlistcore_ic_pause_black : R.drawable.playlistcore_ic_play_arrow_black; playPauseButton.setImageResource(resId); } /** * Used to inform the controls to finalize their setup. This * means replacing the loading animation with the PlayPause button */ public void loadCompleted() { playPauseButton.setVisibility(View.VISIBLE); previousButton.setVisibility(View.VISIBLE); nextButton.setVisibility(View.VISIBLE ); loadingBar.setVisibility(View.INVISIBLE); } /** * Used to inform the controls to return to the loading stage. * This is the opposite of {@link #loadCompleted()} */ public void restartLoading() { playPauseButton.setVisibility(View.INVISIBLE); previousButton.setVisibility(View.INVISIBLE); nextButton.setVisibility(View.INVISIBLE ); loadingBar.setVisibility(View.VISIBLE); } /** * Sets the {@link #seekBar}s max and updates the duration text * * @param duration The duration of the media item in milliseconds */ private void setDuration(long duration) { seekBar.setMax((int)duration); durationView.setText(TimeFormatUtil.formatMs(duration)); } /** * Retrieves the playlist instance and performs any generation * of content if it hasn't already been performed. * * @return True if the content was generated */ private boolean setupPlaylistManager() { playlistManager = App.getPlaylistManager(); //There is nothing to do if the currently playing values are the same if (playlistManager.getId() == PLAYLIST_ID) { return false; } List<MediaItem> mediaItems = new LinkedList<>(); for (Samples.Sample sample : Samples.getAudioSamples()) { MediaItem mediaItem = new MediaItem(sample, true); mediaItems.add(mediaItem); } playlistManager.setParameters(mediaItems, selectedIndex); playlistManager.setId(PLAYLIST_ID); return true; } /** * Populates the class variables with the views created from the * xml layout file. */ private void retrieveViews() { loadingBar = (ProgressBar)findViewById(R.id.audio_player_loading); artworkView = (ImageView)findViewById(R.id.audio_player_image); currentPositionView = (TextView)findViewById(R.id.audio_player_position); durationView = (TextView)findViewById(R.id.audio_player_duration); seekBar = (SeekBar)findViewById(R.id.audio_player_seek); previousButton = (ImageButton)findViewById(R.id.audio_player_previous); playPauseButton = (ImageButton)findViewById(R.id.audio_player_play_pause); nextButton = (ImageButton)findViewById(R.id.audio_player_next); } /** * Links the SeekBarChanged to the {@link #seekBar} and * onClickListeners to the media buttons that call the appropriate * invoke methods in the {@link #playlistManager} */ private void setupListeners() { seekBar.setOnSeekBarChangeListener(new SeekBarChanged()); previousButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { playlistManager.invokePrevious(); } }); playPauseButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { playlistManager.invokePausePlay(); } }); nextButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { playlistManager.invokeNext(); } }); } /** * Starts the audio playback if necessary. * * @param forceStart True if the audio should be started from the beginning even if it is currently playing */ private void startPlayback(boolean forceStart) { //If we are changing audio files, or we haven't played before then start the playback if (forceStart || playlistManager.getCurrentPosition() != selectedIndex) { playlistManager.setCurrentPosition(selectedIndex); playlistManager.play(0, false); } } /** * Listens to the seek bar change events and correctly handles the changes */ private class SeekBarChanged implements SeekBar.OnSeekBarChangeListener { private int seekPosition = -1; @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { if (!fromUser) { return; } seekPosition = progress; currentPositionView.setText(TimeFormatUtil.formatMs(progress)); } @Override public void onStartTrackingTouch(SeekBar seekBar) { userInteracting = true; seekPosition = seekBar.getProgress(); playlistManager.invokeSeekStarted(); } @Override public void onStopTrackingTouch(SeekBar seekBar) { userInteracting = false; //noinspection Range - seekPosition won't be less than 0 playlistManager.invokeSeekEnded(seekPosition); seekPosition = -1; } } }