NewPipe/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHolder.java

300 lines
9.8 KiB
Java
Raw Normal View History

package org.schabi.newpipe.player.helper;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.util.Log;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import com.google.android.exoplayer2.PlaybackException;
import com.google.android.exoplayer2.PlaybackParameters;
import org.schabi.newpipe.App;
import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.extractor.stream.StreamInfo;
2022-04-08 09:35:14 +02:00
import org.schabi.newpipe.player.PlayerService;
2021-01-08 18:35:33 +01:00
import org.schabi.newpipe.player.Player;
import org.schabi.newpipe.player.PlayerType;
import org.schabi.newpipe.player.event.PlayerServiceEventListener;
import org.schabi.newpipe.player.event.PlayerServiceExtendedEventListener;
import org.schabi.newpipe.player.playqueue.PlayQueue;
public final class PlayerHolder {
private PlayerHolder() {
}
private static PlayerHolder instance;
public static synchronized PlayerHolder getInstance() {
if (PlayerHolder.instance == null) {
PlayerHolder.instance = new PlayerHolder();
}
return PlayerHolder.instance;
}
private static final boolean DEBUG = MainActivity.DEBUG;
private static final String TAG = PlayerHolder.class.getSimpleName();
@Nullable private PlayerServiceExtendedEventListener listener;
private final PlayerServiceConnection serviceConnection = new PlayerServiceConnection();
2021-12-21 00:18:58 +01:00
private boolean bound;
2022-04-08 09:35:14 +02:00
@Nullable private PlayerService playerService;
@Nullable private Player player;
/**
* Returns the current {@link PlayerType} of the {@link PlayerService} service,
* otherwise `null` if no service is running.
*
* @return Current PlayerType
*/
@Nullable
public PlayerType getType() {
if (player == null) {
return null;
}
return player.getPlayerType();
}
public boolean isPlaying() {
if (player == null) {
return false;
}
return player.isPlaying();
}
public boolean isPlayerOpen() {
return player != null;
}
/**
* Use this method to only allow the user to manipulate the play queue (e.g. by enqueueing via
* the stream long press menu) when there actually is a play queue to manipulate.
* @return true only if the player is open and its play queue is ready (i.e. it is not null)
*/
public boolean isPlayQueueReady() {
return player != null && player.getPlayQueue() != null;
}
2021-12-21 00:18:58 +01:00
public boolean isBound() {
return bound;
}
Add play next to long press menu & refactor enqueue methods (#6872) * added mvp play next button in long press menu; new intent handling, new long press dialog entry, new dialog functions, new strings * changed line length for checkstyle pass * cleaned comments, moved strings * Update app/src/main/res/values/strings.xml to make long press entry more descriptive Co-authored-by: opusforlife2 <53176348+opusforlife2@users.noreply.github.com> * Update app/src/main/res/values/strings.xml Co-authored-by: Stypox <stypox@pm.me> * replace redundant nextOnVideoPlayer methods Co-authored-by: Stypox <stypox@pm.me> * add enqueueNextOnPlayer and enqueueOnPlayer without selectOnAppend and RESUME_PLAYBACK/ deprecate enqueueNextOn*Player and enqueueOn*Player methods add getPlayerIntent, getPlayerEnqueueIntent and getPlayerEnqueueNextIntent without selectOnAppend and RESUME_PLAYBACK/ deprecate those with add section comments * removed deprecated methods removed redundant methods * removed deprecated methods removed redundant methods * replaced APPEND_ONLY, removed SELECT_ON_APPEND / replaced remaining enqueueOn*Player methods * now works with playlists * renamed dialog entry * checking for >1 items in the queue using the PlayerHolder * making enqueue*OnPlayer safe to call when no video is playing (defaulting to audio) * corrected strings * improve getQueueSize in PlayerHolder * long press to enqueue only if queue isnt empty * add Whitespace Co-authored-by: Stypox <stypox@pm.me> * clarify comments / add spaces * PlayerType as parameter of the enqueueOnPlayer method add Helper method * using the helper function everywhere (except for the background and popup long-press actions (also on playlists, history, ...)), so basically nowhere / passing checkstyle * assimilated the enqueue*OnPlayer methods * removed redundant comment, variable * simplify code line Co-authored-by: Stypox <stypox@pm.me> * move if * replace workaround for isPlayerOpen() Co-authored-by: Stypox <stypox@pm.me> * replaced workarounds (getType), corrected static access with getInstance * remove unused imports * changed method call to original, new method doesnt exist yet. * Use getter method instead of property access syntax. * improve conditional for play next entry Co-authored-by: Stypox <stypox@pm.me> * show play next btn in feed fragment Co-authored-by: Stypox <stypox@pm.me> * add play next to local playlist and statistics fragment Co-authored-by: Stypox <stypox@pm.me> * formating Co-authored-by: Stypox <stypox@pm.me> * correcting logic Co-authored-by: Stypox <stypox@pm.me> * remove 2 year old unused string, formating Co-authored-by: Stypox <stypox@pm.me> * correct enqueue (next) conditionals, default to background if no player is open. Dont generally default to background play. * remove player open checks from button long press enqueue actions * improve log msg * Rename next to enqueue_next * Refactor kotlin Co-authored-by: opusforlife2 <53176348+opusforlife2@users.noreply.github.com> Co-authored-by: Stypox <stypox@pm.me>
2021-09-18 11:22:49 +02:00
public int getQueueSize() {
if (player == null || player.getPlayQueue() == null) {
// player play queue might be null e.g. while player is starting
return 0;
}
return player.getPlayQueue().size();
Add play next to long press menu & refactor enqueue methods (#6872) * added mvp play next button in long press menu; new intent handling, new long press dialog entry, new dialog functions, new strings * changed line length for checkstyle pass * cleaned comments, moved strings * Update app/src/main/res/values/strings.xml to make long press entry more descriptive Co-authored-by: opusforlife2 <53176348+opusforlife2@users.noreply.github.com> * Update app/src/main/res/values/strings.xml Co-authored-by: Stypox <stypox@pm.me> * replace redundant nextOnVideoPlayer methods Co-authored-by: Stypox <stypox@pm.me> * add enqueueNextOnPlayer and enqueueOnPlayer without selectOnAppend and RESUME_PLAYBACK/ deprecate enqueueNextOn*Player and enqueueOn*Player methods add getPlayerIntent, getPlayerEnqueueIntent and getPlayerEnqueueNextIntent without selectOnAppend and RESUME_PLAYBACK/ deprecate those with add section comments * removed deprecated methods removed redundant methods * removed deprecated methods removed redundant methods * replaced APPEND_ONLY, removed SELECT_ON_APPEND / replaced remaining enqueueOn*Player methods * now works with playlists * renamed dialog entry * checking for >1 items in the queue using the PlayerHolder * making enqueue*OnPlayer safe to call when no video is playing (defaulting to audio) * corrected strings * improve getQueueSize in PlayerHolder * long press to enqueue only if queue isnt empty * add Whitespace Co-authored-by: Stypox <stypox@pm.me> * clarify comments / add spaces * PlayerType as parameter of the enqueueOnPlayer method add Helper method * using the helper function everywhere (except for the background and popup long-press actions (also on playlists, history, ...)), so basically nowhere / passing checkstyle * assimilated the enqueue*OnPlayer methods * removed redundant comment, variable * simplify code line Co-authored-by: Stypox <stypox@pm.me> * move if * replace workaround for isPlayerOpen() Co-authored-by: Stypox <stypox@pm.me> * replaced workarounds (getType), corrected static access with getInstance * remove unused imports * changed method call to original, new method doesnt exist yet. * Use getter method instead of property access syntax. * improve conditional for play next entry Co-authored-by: Stypox <stypox@pm.me> * show play next btn in feed fragment Co-authored-by: Stypox <stypox@pm.me> * add play next to local playlist and statistics fragment Co-authored-by: Stypox <stypox@pm.me> * formating Co-authored-by: Stypox <stypox@pm.me> * correcting logic Co-authored-by: Stypox <stypox@pm.me> * remove 2 year old unused string, formating Co-authored-by: Stypox <stypox@pm.me> * correct enqueue (next) conditionals, default to background if no player is open. Dont generally default to background play. * remove player open checks from button long press enqueue actions * improve log msg * Rename next to enqueue_next * Refactor kotlin Co-authored-by: opusforlife2 <53176348+opusforlife2@users.noreply.github.com> Co-authored-by: Stypox <stypox@pm.me>
2021-09-18 11:22:49 +02:00
}
public void setListener(@Nullable final PlayerServiceExtendedEventListener newListener) {
listener = newListener;
if (listener == null) {
return;
}
// Force reload data from service
if (player != null) {
listener.onServiceConnected(player, playerService, false);
startPlayerListener();
}
}
// helper to handle context in common place as using the same
// context to bind/unbind a service is crucial
private Context getCommonContext() {
return App.getApp();
}
public void startService(final boolean playAfterConnect,
final PlayerServiceExtendedEventListener newListener) {
final Context context = getCommonContext();
setListener(newListener);
if (bound) {
return;
}
// startService() can be called concurrently and it will give a random crashes
// and NullPointerExceptions inside the service because the service will be
// bound twice. Prevent it with unbinding first
unbind(context);
2022-04-08 09:35:14 +02:00
ContextCompat.startForegroundService(context, new Intent(context, PlayerService.class));
serviceConnection.doPlayAfterConnect(playAfterConnect);
bind(context);
}
public void stopService() {
final Context context = getCommonContext();
unbind(context);
2022-04-08 09:35:14 +02:00
context.stopService(new Intent(context, PlayerService.class));
}
class PlayerServiceConnection implements ServiceConnection {
private boolean playAfterConnect = false;
public void doPlayAfterConnect(final boolean playAfterConnection) {
this.playAfterConnect = playAfterConnection;
}
@Override
public void onServiceDisconnected(final ComponentName compName) {
if (DEBUG) {
Log.d(TAG, "Player service is disconnected");
}
final Context context = getCommonContext();
unbind(context);
}
@Override
public void onServiceConnected(final ComponentName compName, final IBinder service) {
if (DEBUG) {
Log.d(TAG, "Player service is connected");
}
2022-04-08 09:35:14 +02:00
final PlayerService.LocalBinder localBinder = (PlayerService.LocalBinder) service;
playerService = localBinder.getService();
player = localBinder.getPlayer();
if (listener != null) {
listener.onServiceConnected(player, playerService, playAfterConnect);
}
startPlayerListener();
}
}
private void bind(final Context context) {
if (DEBUG) {
Log.d(TAG, "bind() called");
}
2022-04-08 09:35:14 +02:00
final Intent serviceIntent = new Intent(context, PlayerService.class);
bound = context.bindService(serviceIntent, serviceConnection,
Context.BIND_AUTO_CREATE);
if (!bound) {
context.unbindService(serviceConnection);
}
}
private void unbind(final Context context) {
if (DEBUG) {
Log.d(TAG, "unbind() called");
}
if (bound) {
context.unbindService(serviceConnection);
bound = false;
stopPlayerListener();
playerService = null;
player = null;
if (listener != null) {
listener.onServiceDisconnected();
}
}
}
private void startPlayerListener() {
if (player != null) {
player.setFragmentListener(internalListener);
}
}
private void stopPlayerListener() {
if (player != null) {
player.removeFragmentListener(internalListener);
}
}
private final PlayerServiceEventListener internalListener =
new PlayerServiceEventListener() {
2022-04-08 09:35:14 +02:00
@Override
public void onViewCreated() {
if (listener != null) {
listener.onViewCreated();
}
}
@Override
public void onFullscreenStateChanged(final boolean fullscreen) {
if (listener != null) {
listener.onFullscreenStateChanged(fullscreen);
}
}
@Override
public void onScreenRotationButtonClicked() {
if (listener != null) {
listener.onScreenRotationButtonClicked();
}
}
@Override
public void onMoreOptionsLongClicked() {
if (listener != null) {
listener.onMoreOptionsLongClicked();
}
}
@Override
public void onPlayerError(final PlaybackException error,
final boolean isCatchableException) {
if (listener != null) {
listener.onPlayerError(error, isCatchableException);
}
}
@Override
public void hideSystemUiIfNeeded() {
if (listener != null) {
listener.hideSystemUiIfNeeded();
}
}
@Override
public void onQueueUpdate(final PlayQueue queue) {
if (listener != null) {
listener.onQueueUpdate(queue);
}
}
@Override
public void onPlaybackUpdate(final int state,
final int repeatMode,
final boolean shuffled,
final PlaybackParameters parameters) {
if (listener != null) {
listener.onPlaybackUpdate(state, repeatMode, shuffled, parameters);
}
}
@Override
public void onProgressUpdate(final int currentProgress,
final int duration,
final int bufferPercent) {
if (listener != null) {
listener.onProgressUpdate(currentProgress, duration, bufferPercent);
}
}
@Override
public void onMetadataUpdate(final StreamInfo info, final PlayQueue queue) {
if (listener != null) {
listener.onMetadataUpdate(info, queue);
}
}
@Override
public void onServiceStopped() {
if (listener != null) {
listener.onServiceStopped();
}
unbind(getCommonContext());
}
};
}