NewPipe/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayerActivity.java

536 lines
20 KiB
Java

package org.schabi.newpipe.player;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.provider.Settings;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.support.v7.widget.helper.ItemTouchHelper;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.ImageButton;
import android.widget.PopupMenu;
import android.widget.ProgressBar;
import android.widget.SeekBar;
import android.widget.TextView;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.playlist.PlayQueueItem;
import org.schabi.newpipe.playlist.PlayQueueItemBuilder;
import org.schabi.newpipe.playlist.PlayQueueItemHolder;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.ThemeHelper;
public class BackgroundPlayerActivity extends AppCompatActivity
implements BackgroundPlayer.PlayerEventListener, SeekBar.OnSeekBarChangeListener, View.OnClickListener {
private static final String TAG = "BGPlayerActivity";
private boolean serviceBound;
private ServiceConnection serviceConnection;
private BackgroundPlayer.BasePlayerImpl player;
private boolean seeking;
////////////////////////////////////////////////////////////////////////////
// Views
////////////////////////////////////////////////////////////////////////////
private static final int RECYCLER_ITEM_POPUP_MENU_GROUP_ID = 47;
private static final int PLAYBACK_SPEED_POPUP_MENU_GROUP_ID = 61;
private static final int PLAYBACK_PITCH_POPUP_MENU_GROUP_ID = 97;
private View rootView;
private RecyclerView itemsList;
private ItemTouchHelper itemTouchHelper;
private TextView metadataTitle;
private TextView metadataArtist;
private SeekBar progressSeekBar;
private TextView progressCurrentTime;
private TextView progressEndTime;
private ImageButton repeatButton;
private ImageButton backwardButton;
private ImageButton playPauseButton;
private ImageButton forwardButton;
private ImageButton shuffleButton;
private ProgressBar progressBar;
private TextView playbackSpeedButton;
private PopupMenu playbackSpeedPopupMenu;
private TextView playbackPitchButton;
private PopupMenu playbackPitchPopupMenu;
////////////////////////////////////////////////////////////////////////////
// Activity Lifecycle
////////////////////////////////////////////////////////////////////////////
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ThemeHelper.setTheme(this);
setContentView(R.layout.activity_background_player);
rootView = findViewById(R.id.main_content);
final Toolbar toolbar = rootView.findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setTitle(R.string.title_activity_background_player);
}
serviceConnection = backgroundPlayerConnection();
bind();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_play_queue, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
finish();
return true;
case R.id.action_history:
NavigationHelper.openHistory(this);
return true;
case R.id.action_settings:
NavigationHelper.openSettings(this);
return true;
case R.id.action_system_audio:
startActivity(new Intent(Settings.ACTION_SOUND_SETTINGS));
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
protected void onDestroy() {
super.onDestroy();
unbind();
}
////////////////////////////////////////////////////////////////////////////
// Service Connection
////////////////////////////////////////////////////////////////////////////
private void bind() {
final Intent mIntent = new Intent(this, BackgroundPlayer.class);
final boolean success = bindService(mIntent, serviceConnection, BIND_AUTO_CREATE);
if (!success) {
unbindService(serviceConnection);
}
serviceBound = success;
}
private void unbind() {
if(serviceBound) {
unbindService(serviceConnection);
serviceBound = false;
stopPlayerListener();
player = null;
finish();
}
}
private ServiceConnection backgroundPlayerConnection() {
return new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
Log.d(TAG, "Background player service is disconnected");
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d(TAG, "Background player service is connected");
final BackgroundPlayer.LocalBinder mLocalBinder = (BackgroundPlayer.LocalBinder) service;
player = mLocalBinder.getBackgroundPlayerInstance();
if (player == null || player.playQueue == null || player.playQueueAdapter == null || player.simpleExoPlayer == null) {
unbind();
} else {
buildComponents();
startPlayerListener();
}
}
};
}
////////////////////////////////////////////////////////////////////////////
// Component Building
////////////////////////////////////////////////////////////////////////////
private void buildComponents() {
buildQueue();
buildMetadata();
buildSeekBar();
buildControls();
}
private void buildQueue() {
itemsList = findViewById(R.id.play_queue);
itemsList.setLayoutManager(new LinearLayoutManager(this));
itemsList.setAdapter(player.playQueueAdapter);
itemsList.setClickable(true);
itemsList.setLongClickable(true);
itemTouchHelper = new ItemTouchHelper(getItemTouchCallback());
itemTouchHelper.attachToRecyclerView(itemsList);
player.playQueueAdapter.setSelectedListener(getOnSelectedListener());
}
private void buildMetadata() {
metadataTitle = rootView.findViewById(R.id.song_name);
metadataArtist = rootView.findViewById(R.id.artist_name);
}
private void buildSeekBar() {
progressCurrentTime = rootView.findViewById(R.id.current_time);
progressSeekBar = rootView.findViewById(R.id.seek_bar);
progressEndTime = rootView.findViewById(R.id.end_time);
progressSeekBar.setOnSeekBarChangeListener(this);
}
private void buildControls() {
repeatButton = rootView.findViewById(R.id.control_repeat);
backwardButton = rootView.findViewById(R.id.control_backward);
playPauseButton = rootView.findViewById(R.id.control_play_pause);
forwardButton = rootView.findViewById(R.id.control_forward);
shuffleButton = rootView.findViewById(R.id.control_shuffle);
playbackSpeedButton = rootView.findViewById(R.id.control_playback_speed);
playbackPitchButton = rootView.findViewById(R.id.control_playback_pitch);
progressBar = rootView.findViewById(R.id.control_progress_bar);
repeatButton.setOnClickListener(this);
backwardButton.setOnClickListener(this);
playPauseButton.setOnClickListener(this);
forwardButton.setOnClickListener(this);
shuffleButton.setOnClickListener(this);
playbackSpeedButton.setOnClickListener(this);
playbackPitchButton.setOnClickListener(this);
playbackSpeedPopupMenu = new PopupMenu(this, playbackSpeedButton);
playbackPitchPopupMenu = new PopupMenu(this, playbackPitchButton);
buildPlaybackSpeedMenu();
buildPlaybackPitchMenu();
}
private void buildPlaybackSpeedMenu() {
if (playbackSpeedPopupMenu == null) return;
playbackSpeedPopupMenu.getMenu().removeGroup(PLAYBACK_SPEED_POPUP_MENU_GROUP_ID);
for (int i = 0; i < BasePlayer.PLAYBACK_SPEEDS.length; i++) {
final float playbackSpeed = BasePlayer.PLAYBACK_SPEEDS[i];
final String formattedSpeed = player.formatSpeed(playbackSpeed);
final MenuItem item = playbackSpeedPopupMenu.getMenu().add(PLAYBACK_SPEED_POPUP_MENU_GROUP_ID, i, Menu.NONE, formattedSpeed);
item.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
player.setPlaybackSpeed(playbackSpeed);
return true;
}
});
}
}
private void buildPlaybackPitchMenu() {
if (playbackPitchPopupMenu == null) return;
playbackPitchPopupMenu.getMenu().removeGroup(PLAYBACK_PITCH_POPUP_MENU_GROUP_ID);
for (int i = 0; i < BasePlayer.PLAYBACK_PITCHES.length; i++) {
final float playbackPitch = BasePlayer.PLAYBACK_PITCHES[i];
final String formattedPitch = player.formatPitch(playbackPitch);
final MenuItem item = playbackPitchPopupMenu.getMenu().add(PLAYBACK_PITCH_POPUP_MENU_GROUP_ID, i, Menu.NONE, formattedPitch);
item.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
player.setPlaybackPitch(playbackPitch);
return true;
}
});
}
}
private void buildItemPopupMenu(final PlayQueueItem item, final View view) {
final PopupMenu menu = new PopupMenu(this, view);
final MenuItem remove = menu.getMenu().add(RECYCLER_ITEM_POPUP_MENU_GROUP_ID, 0, Menu.NONE, R.string.play_queue_remove);
remove.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
final int index = player.playQueue.indexOf(item);
if (index != -1) player.playQueue.remove(index);
return true;
}
});
final MenuItem detail = menu.getMenu().add(RECYCLER_ITEM_POPUP_MENU_GROUP_ID, 1, Menu.NONE, R.string.play_queue_stream_detail);
detail.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
onOpenDetail(item.getServiceId(), item.getUrl(), item.getTitle());
return true;
}
});
menu.show();
}
////////////////////////////////////////////////////////////////////////////
// Component Helpers
////////////////////////////////////////////////////////////////////////////
private ItemTouchHelper.SimpleCallback getItemTouchCallback() {
return new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN, 0) {
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder source, RecyclerView.ViewHolder target) {
if (source.getItemViewType() != target.getItemViewType()) {
return false;
}
final int sourceIndex = source.getLayoutPosition();
final int targetIndex = target.getLayoutPosition();
player.playQueue.move(sourceIndex, targetIndex);
return true;
}
@Override
public boolean isLongPressDragEnabled() {
return false;
}
@Override
public boolean isItemViewSwipeEnabled() {
return false;
}
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int swipeDir) {}
};
}
private PlayQueueItemBuilder.OnSelectedListener getOnSelectedListener() {
return new PlayQueueItemBuilder.OnSelectedListener() {
@Override
public void selected(PlayQueueItem item, View view) {
final int index = player.playQueue.indexOf(item);
if (index == -1) return;
if (player.playQueue.getIndex() == index) {
player.onRestart();
} else {
player.playQueue.setIndex(index);
}
}
@Override
public void held(PlayQueueItem item, View view) {
final int index = player.playQueue.indexOf(item);
if (index != -1) buildItemPopupMenu(item, view);
}
@Override
public void onStartDrag(PlayQueueItemHolder viewHolder) {
if (itemTouchHelper != null) itemTouchHelper.startDrag(viewHolder);
}
};
}
private void onOpenDetail(int serviceId, String videoUrl, String videoTitle) {
NavigationHelper.openVideoDetail(this, serviceId, videoUrl, videoTitle);
}
private void scrollToSelected() {
itemsList.smoothScrollToPosition(player.playQueue.getIndex());
}
////////////////////////////////////////////////////////////////////////////
// Component On-Click Listener
////////////////////////////////////////////////////////////////////////////
@Override
public void onClick(View view) {
if (view.getId() == repeatButton.getId()) {
player.onRepeatClicked();
} else if (view.getId() == backwardButton.getId()) {
player.onPlayPrevious();
scrollToSelected();
} else if (view.getId() == playPauseButton.getId()) {
player.onVideoPlayPause();
scrollToSelected();
} else if (view.getId() == forwardButton.getId()) {
player.onPlayNext();
} else if (view.getId() == shuffleButton.getId()) {
player.onShuffleClicked();
} else if (view.getId() == playbackSpeedButton.getId()) {
playbackSpeedPopupMenu.show();
} else if (view.getId() == playbackPitchButton.getId()) {
playbackPitchPopupMenu.show();
}
}
////////////////////////////////////////////////////////////////////////////
// Seekbar Listener
////////////////////////////////////////////////////////////////////////////
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (fromUser) progressCurrentTime.setText(Localization.getDurationString(progress / 1000));
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
seeking = true;
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
player.simpleExoPlayer.seekTo(seekBar.getProgress());
seeking = false;
}
////////////////////////////////////////////////////////////////////////////
// Binding Service Listener
////////////////////////////////////////////////////////////////////////////
private void startPlayerListener() {
if (player != null) {
player.setActivityListener(this);
}
}
private void stopPlayerListener() {
if (player != null) {
player.removeActivityListener(this);
}
}
@Override
public void onPlaybackUpdate(int state, int repeatMode, boolean shuffled, PlaybackParameters parameters) {
onStateChanged(state);
onPlayModeChanged(repeatMode, shuffled);
onPlaybackParameterChanged(parameters);
scrollToSelected();
}
@Override
public void onProgressUpdate(int currentProgress, int duration, int bufferPercent) {
// Set buffer progress
progressSeekBar.setSecondaryProgress((int) (progressSeekBar.getMax() * ((float) bufferPercent / 100)));
// Set Duration
progressSeekBar.setMax(duration);
progressEndTime.setText(Localization.getDurationString(duration / 1000));
// Set current time if not seeking
if (!seeking) {
progressSeekBar.setProgress(currentProgress);
progressCurrentTime.setText(Localization.getDurationString(currentProgress / 1000));
}
}
@Override
public void onMetadataUpdate(StreamInfo info) {
if (info != null) {
metadataTitle.setText(info.name);
metadataArtist.setText(info.uploader_name);
scrollToSelected();
}
}
@Override
public void onServiceStopped() {
unbind();
}
////////////////////////////////////////////////////////////////////////////
// Binding Service Helper
////////////////////////////////////////////////////////////////////////////
private void onStateChanged(final int state) {
switch (state) {
case BasePlayer.STATE_PAUSED:
playPauseButton.setImageResource(R.drawable.ic_play_arrow_white);
break;
case BasePlayer.STATE_PLAYING:
playPauseButton.setImageResource(R.drawable.ic_pause_white);
break;
case BasePlayer.STATE_COMPLETED:
playPauseButton.setImageResource(R.drawable.ic_replay_white);
break;
default:
break;
}
switch (state) {
case BasePlayer.STATE_PAUSED:
case BasePlayer.STATE_PLAYING:
case BasePlayer.STATE_COMPLETED:
playPauseButton.setClickable(true);
playPauseButton.setVisibility(View.VISIBLE);
progressBar.setVisibility(View.GONE);
break;
default:
playPauseButton.setClickable(false);
playPauseButton.setVisibility(View.INVISIBLE);
progressBar.setVisibility(View.VISIBLE);
break;
}
}
private void onPlayModeChanged(final int repeatMode, final boolean shuffled) {
switch (repeatMode) {
case Player.REPEAT_MODE_OFF:
repeatButton.setImageResource(R.drawable.exo_controls_repeat_off);
break;
case Player.REPEAT_MODE_ONE:
repeatButton.setImageResource(R.drawable.exo_controls_repeat_one);
break;
case Player.REPEAT_MODE_ALL:
repeatButton.setImageResource(R.drawable.exo_controls_repeat_all);
break;
}
final int shuffleAlpha = shuffled ? 255 : 77;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
shuffleButton.setImageAlpha(shuffleAlpha);
} else {
shuffleButton.setAlpha(shuffleAlpha);
}
}
private void onPlaybackParameterChanged(final PlaybackParameters parameters) {
if (parameters != null) {
playbackSpeedButton.setText(player.formatSpeed(parameters.speed));
playbackPitchButton.setText(player.formatPitch(parameters.pitch));
}
}
}