2020-09-17 22:42:35 +02:00
|
|
|
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;
|
2020-10-06 13:33:44 +02:00
|
|
|
|
|
|
|
import androidx.annotation.Nullable;
|
2021-06-24 10:00:56 +02:00
|
|
|
import androidx.core.content.ContextCompat;
|
2020-10-06 13:33:44 +02:00
|
|
|
|
2022-03-13 06:22:47 +01:00
|
|
|
import com.google.android.exoplayer2.PlaybackException;
|
2020-09-17 22:42:35 +02:00
|
|
|
import com.google.android.exoplayer2.PlaybackParameters;
|
2020-10-06 13:33:44 +02:00
|
|
|
|
2020-09-17 22:42:35 +02:00
|
|
|
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;
|
2022-07-10 23:05:37 +02:00
|
|
|
import org.schabi.newpipe.player.PlayerType;
|
2020-09-17 22:42:35 +02:00
|
|
|
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 {
|
2021-06-24 10:00:56 +02:00
|
|
|
|
2020-09-17 22:42:35 +02:00
|
|
|
private PlayerHolder() {
|
|
|
|
}
|
|
|
|
|
2021-06-24 10:00:56 +02:00
|
|
|
private static PlayerHolder instance;
|
|
|
|
public static synchronized PlayerHolder getInstance() {
|
|
|
|
if (PlayerHolder.instance == null) {
|
|
|
|
PlayerHolder.instance = new PlayerHolder();
|
|
|
|
}
|
|
|
|
return PlayerHolder.instance;
|
|
|
|
}
|
|
|
|
|
2021-12-21 20:54:06 +01:00
|
|
|
private static final boolean DEBUG = MainActivity.DEBUG;
|
|
|
|
private static final String TAG = PlayerHolder.class.getSimpleName();
|
2020-09-17 22:42:35 +02:00
|
|
|
|
2022-01-25 17:37:15 +01:00
|
|
|
@Nullable private PlayerServiceExtendedEventListener listener;
|
2020-09-17 22:42:35 +02:00
|
|
|
|
2021-06-24 10:00:56 +02:00
|
|
|
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;
|
2022-01-25 17:37:15 +01:00
|
|
|
@Nullable private Player player;
|
2020-09-17 22:42:35 +02:00
|
|
|
|
2020-10-06 13:33:44 +02:00
|
|
|
/**
|
2022-07-10 23:05:37 +02:00
|
|
|
* Returns the current {@link PlayerType} of the {@link PlayerService} service,
|
|
|
|
* otherwise `null` if no service is running.
|
2020-10-06 14:38:48 +02:00
|
|
|
*
|
|
|
|
* @return Current PlayerType
|
2020-10-06 13:33:44 +02:00
|
|
|
*/
|
|
|
|
@Nullable
|
2022-07-10 23:05:37 +02:00
|
|
|
public PlayerType getType() {
|
2020-10-06 13:33:44 +02:00
|
|
|
if (player == null) {
|
|
|
|
return null;
|
|
|
|
}
|
2020-10-06 17:22:12 +02:00
|
|
|
return player.getPlayerType();
|
2020-10-06 13:33:44 +02:00
|
|
|
}
|
|
|
|
|
2021-06-24 10:00:56 +02:00
|
|
|
public boolean isPlaying() {
|
2020-10-31 20:26:09 +01:00
|
|
|
if (player == null) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return player.isPlaying();
|
|
|
|
}
|
|
|
|
|
2021-06-24 10:00:56 +02:00
|
|
|
public boolean isPlayerOpen() {
|
2020-11-01 21:45:39 +01:00
|
|
|
return player != null;
|
|
|
|
}
|
|
|
|
|
2022-01-27 17:11:16 +01:00
|
|
|
/**
|
|
|
|
* 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() {
|
2022-01-25 17:37:15 +01:00
|
|
|
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
|
|
|
}
|
|
|
|
|
2021-06-24 10:00:56 +02:00
|
|
|
public void setListener(@Nullable final PlayerServiceExtendedEventListener newListener) {
|
2020-09-17 22:42:35 +02:00
|
|
|
listener = newListener;
|
2021-06-24 10:00:56 +02:00
|
|
|
|
|
|
|
if (listener == null) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-09-17 22:42:35 +02:00
|
|
|
// Force reload data from service
|
|
|
|
if (player != null) {
|
|
|
|
listener.onServiceConnected(player, playerService, false);
|
|
|
|
startPlayerListener();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-24 10:00:56 +02:00
|
|
|
// 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();
|
2020-09-17 22:42:35 +02:00
|
|
|
}
|
|
|
|
|
2021-06-24 10:00:56 +02:00
|
|
|
public void startService(final boolean playAfterConnect,
|
|
|
|
final PlayerServiceExtendedEventListener newListener) {
|
|
|
|
final Context context = getCommonContext();
|
2020-09-17 22:42:35 +02:00
|
|
|
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));
|
2021-06-24 10:00:56 +02:00
|
|
|
serviceConnection.doPlayAfterConnect(playAfterConnect);
|
2020-09-17 22:42:35 +02:00
|
|
|
bind(context);
|
|
|
|
}
|
|
|
|
|
2021-06-24 10:00:56 +02:00
|
|
|
public void stopService() {
|
|
|
|
final Context context = getCommonContext();
|
2020-09-17 22:42:35 +02:00
|
|
|
unbind(context);
|
2022-04-08 09:35:14 +02:00
|
|
|
context.stopService(new Intent(context, PlayerService.class));
|
2020-09-17 22:42:35 +02:00
|
|
|
}
|
|
|
|
|
2021-06-24 10:00:56 +02:00
|
|
|
class PlayerServiceConnection implements ServiceConnection {
|
|
|
|
|
|
|
|
private boolean playAfterConnect = false;
|
|
|
|
|
|
|
|
public void doPlayAfterConnect(final boolean playAfterConnection) {
|
|
|
|
this.playAfterConnect = playAfterConnection;
|
|
|
|
}
|
2020-09-17 22:42:35 +02:00
|
|
|
|
2021-06-24 10:00:56 +02:00
|
|
|
@Override
|
|
|
|
public void onServiceDisconnected(final ComponentName compName) {
|
|
|
|
if (DEBUG) {
|
|
|
|
Log.d(TAG, "Player service is disconnected");
|
2020-09-17 22:42:35 +02:00
|
|
|
}
|
|
|
|
|
2021-06-24 10:00:56 +02:00
|
|
|
final Context context = getCommonContext();
|
|
|
|
unbind(context);
|
|
|
|
}
|
2020-09-17 22:42:35 +02:00
|
|
|
|
2021-06-24 10:00:56 +02:00
|
|
|
@Override
|
|
|
|
public void onServiceConnected(final ComponentName compName, final IBinder service) {
|
|
|
|
if (DEBUG) {
|
|
|
|
Log.d(TAG, "Player service is connected");
|
2020-09-17 22:42:35 +02:00
|
|
|
}
|
2022-04-08 09:35:14 +02:00
|
|
|
final PlayerService.LocalBinder localBinder = (PlayerService.LocalBinder) service;
|
2021-06-24 10:00:56 +02:00
|
|
|
|
|
|
|
playerService = localBinder.getService();
|
|
|
|
player = localBinder.getPlayer();
|
|
|
|
if (listener != null) {
|
|
|
|
listener.onServiceConnected(player, playerService, playAfterConnect);
|
|
|
|
}
|
|
|
|
startPlayerListener();
|
|
|
|
}
|
2021-12-21 20:54:06 +01:00
|
|
|
}
|
2020-09-17 22:42:35 +02:00
|
|
|
|
2021-06-24 10:00:56 +02:00
|
|
|
private void bind(final Context context) {
|
2020-09-17 22:42:35 +02:00
|
|
|
if (DEBUG) {
|
|
|
|
Log.d(TAG, "bind() called");
|
|
|
|
}
|
|
|
|
|
2022-04-08 09:35:14 +02:00
|
|
|
final Intent serviceIntent = new Intent(context, PlayerService.class);
|
2021-06-24 10:00:56 +02:00
|
|
|
bound = context.bindService(serviceIntent, serviceConnection,
|
|
|
|
Context.BIND_AUTO_CREATE);
|
2020-09-17 22:42:35 +02:00
|
|
|
if (!bound) {
|
|
|
|
context.unbindService(serviceConnection);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-24 10:00:56 +02:00
|
|
|
private void unbind(final Context context) {
|
2020-09-17 22:42:35 +02:00
|
|
|
if (DEBUG) {
|
|
|
|
Log.d(TAG, "unbind() called");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (bound) {
|
|
|
|
context.unbindService(serviceConnection);
|
|
|
|
bound = false;
|
|
|
|
stopPlayerListener();
|
|
|
|
playerService = null;
|
|
|
|
player = null;
|
|
|
|
if (listener != null) {
|
|
|
|
listener.onServiceDisconnected();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-24 10:00:56 +02:00
|
|
|
private void startPlayerListener() {
|
2020-09-17 22:42:35 +02:00
|
|
|
if (player != null) {
|
2021-06-24 10:00:56 +02:00
|
|
|
player.setFragmentListener(internalListener);
|
2020-09-17 22:42:35 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-24 10:00:56 +02:00
|
|
|
private void stopPlayerListener() {
|
2020-09-17 22:42:35 +02:00
|
|
|
if (player != null) {
|
2021-06-24 10:00:56 +02:00
|
|
|
player.removeFragmentListener(internalListener);
|
2020-09-17 22:42:35 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-24 10:00:56 +02:00
|
|
|
private final PlayerServiceEventListener internalListener =
|
2020-09-17 22:42:35 +02:00
|
|
|
new PlayerServiceEventListener() {
|
2022-04-08 09:35:14 +02:00
|
|
|
@Override
|
|
|
|
public void onViewCreated() {
|
|
|
|
if (listener != null) {
|
|
|
|
listener.onViewCreated();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-17 22:42:35 +02:00
|
|
|
@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
|
2022-03-13 06:22:47 +01:00
|
|
|
public void onPlayerError(final PlaybackException error,
|
|
|
|
final boolean isCatchableException) {
|
2020-09-17 22:42:35 +02:00
|
|
|
if (listener != null) {
|
2022-03-13 06:22:47 +01:00
|
|
|
listener.onPlayerError(error, isCatchableException);
|
2020-09-17 22:42:35 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@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();
|
|
|
|
}
|
2021-06-24 10:00:56 +02:00
|
|
|
unbind(getCommonContext());
|
2020-09-17 22:42:35 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|