2
0
mirror of https://github.com/TeamNewPipe/NewPipe synced 2025-08-29 05:37:49 +00:00

PlayerHolder: use object class to implement singleton pattern

This commit is contained in:
Profpatsch 2025-01-01 17:46:48 +01:00 committed by Stypox
parent cc3ecd4169
commit 38ed1da79e
No known key found for this signature in database
GPG Key ID: 4BDF1B40A49FDD23
7 changed files with 43 additions and 53 deletions

View File

@ -849,7 +849,7 @@ public class MainActivity extends AppCompatActivity {
return; return;
} }
if (PlayerHolder.Companion.getInstance().isPlayerOpen()) { if (PlayerHolder.INSTANCE.isPlayerOpen()) {
// if the player is already open, no need for a broadcast receiver // if the player is already open, no need for a broadcast receiver
openMiniPlayerIfMissing(); openMiniPlayerIfMissing();
} else { } else {
@ -859,7 +859,7 @@ public class MainActivity extends AppCompatActivity {
public void onReceive(final Context context, final Intent intent) { public void onReceive(final Context context, final Intent intent) {
if (Objects.equals(intent.getAction(), if (Objects.equals(intent.getAction(),
VideoDetailFragment.ACTION_PLAYER_STARTED) VideoDetailFragment.ACTION_PLAYER_STARTED)
&& PlayerHolder.Companion.getInstance().isPlayerOpen()) { && PlayerHolder.INSTANCE.isPlayerOpen()) {
openMiniPlayerIfMissing(); openMiniPlayerIfMissing();
// At this point the player is added 100%, we can unregister. Other actions // At this point the player is added 100%, we can unregister. Other actions
// are useless since the fragment will not be removed after that. // are useless since the fragment will not be removed after that.
@ -874,7 +874,7 @@ public class MainActivity extends AppCompatActivity {
// If the PlayerHolder is not bound yet, but the service is running, try to bind to it. // If the PlayerHolder is not bound yet, but the service is running, try to bind to it.
// Once the connection is established, the ACTION_PLAYER_STARTED will be sent. // Once the connection is established, the ACTION_PLAYER_STARTED will be sent.
PlayerHolder.Companion.getInstance().tryBindIfNeeded(this); PlayerHolder.INSTANCE.tryBindIfNeeded(this);
} }
} }

View File

@ -701,7 +701,7 @@ public class RouterActivity extends AppCompatActivity {
} }
// ...the player is not running or in normal Video-mode/type // ...the player is not running or in normal Video-mode/type
final PlayerType playerType = PlayerHolder.Companion.getInstance().getType(); final PlayerType playerType = PlayerHolder.INSTANCE.getType();
return playerType == null || playerType == PlayerType.MAIN; return playerType == null || playerType == PlayerType.MAIN;
} }

View File

@ -100,7 +100,7 @@ import org.schabi.newpipe.player.PlayerType
import org.schabi.newpipe.player.event.OnKeyDownListener import org.schabi.newpipe.player.event.OnKeyDownListener
import org.schabi.newpipe.player.event.PlayerServiceExtendedEventListener import org.schabi.newpipe.player.event.PlayerServiceExtendedEventListener
import org.schabi.newpipe.player.helper.PlayerHelper import org.schabi.newpipe.player.helper.PlayerHelper
import org.schabi.newpipe.player.helper.PlayerHolder.Companion.getInstance import org.schabi.newpipe.player.helper.PlayerHolder
import org.schabi.newpipe.player.playqueue.PlayQueue import org.schabi.newpipe.player.playqueue.PlayQueue
import org.schabi.newpipe.player.playqueue.SinglePlayQueue import org.schabi.newpipe.player.playqueue.SinglePlayQueue
import org.schabi.newpipe.player.playqueue.events.PlayQueueEvent import org.schabi.newpipe.player.playqueue.events.PlayQueueEvent
@ -212,7 +212,6 @@ class VideoDetailFragment :
private var settingsContentObserver: ContentObserver? = null private var settingsContentObserver: ContentObserver? = null
private var playerService: PlayerService? = null private var playerService: PlayerService? = null
private var player: Player? = null private var player: Player? = null
private val playerHolder = getInstance()
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// Service management // Service management
@ -367,9 +366,9 @@ class VideoDetailFragment :
// Stop the service when user leaves the app with double back press // Stop the service when user leaves the app with double back press
// if video player is selected. Otherwise unbind // if video player is selected. Otherwise unbind
if (activity.isFinishing() && this.isPlayerAvailable && player!!.videoPlayerSelected()) { if (activity.isFinishing() && this.isPlayerAvailable && player!!.videoPlayerSelected()) {
playerHolder.stopService() PlayerHolder.stopService()
} else { } else {
playerHolder.setListener(null) PlayerHolder.setListener(null)
} }
PreferenceManager.getDefaultSharedPreferences(activity) PreferenceManager.getDefaultSharedPreferences(activity)
@ -768,10 +767,10 @@ class VideoDetailFragment :
) )
setupBottomPlayer() setupBottomPlayer()
if (!playerHolder.isBound) { if (!PlayerHolder.isBound) {
setHeightThumbnail() setHeightThumbnail()
} else { } else {
playerHolder.startService(false, this) PlayerHolder.startService(false, this)
} }
} }
@ -1175,7 +1174,7 @@ class VideoDetailFragment :
// See UI changes while remote playQueue changes // See UI changes while remote playQueue changes
if (!this.isPlayerAvailable) { if (!this.isPlayerAvailable) {
playerHolder.startService(false, this) PlayerHolder.startService(false, this)
} else { } else {
// FIXME Workaround #7427 // FIXME Workaround #7427
player!!.setRecovery() player!!.setRecovery()
@ -1245,7 +1244,7 @@ class VideoDetailFragment :
private fun openNormalBackgroundPlayer(append: Boolean) { private fun openNormalBackgroundPlayer(append: Boolean) {
// See UI changes while remote playQueue changes // See UI changes while remote playQueue changes
if (!this.isPlayerAvailable) { if (!this.isPlayerAvailable) {
playerHolder.startService(false, this) PlayerHolder.startService(false, this)
} }
val queue = setupPlayQueueForIntent(append) val queue = setupPlayQueueForIntent(append)
@ -1263,7 +1262,7 @@ class VideoDetailFragment :
private fun openMainPlayer() { private fun openMainPlayer() {
if (noPlayerServiceAvailable()) { if (noPlayerServiceAvailable()) {
playerHolder.startService(autoPlayEnabled, this) PlayerHolder.startService(autoPlayEnabled, this)
return return
} }
if (currentInfo == null) { if (currentInfo == null) {
@ -1298,7 +1297,7 @@ class VideoDetailFragment :
playerService!!.stopForImmediateReusing() playerService!!.stopForImmediateReusing()
root.ifPresent(Consumer { view: View -> view.setVisibility(View.GONE) }) root.ifPresent(Consumer { view: View -> view.setVisibility(View.GONE) })
} else { } else {
playerHolder.stopService() PlayerHolder.stopService()
} }
} }
@ -1551,8 +1550,8 @@ class VideoDetailFragment :
bottomSheetBehavior!!.setState(BottomSheetBehavior.STATE_COLLAPSED) bottomSheetBehavior!!.setState(BottomSheetBehavior.STATE_COLLAPSED)
} }
// Rebound to the service if it was closed via notification or mini player // Rebound to the service if it was closed via notification or mini player
if (!playerHolder.isBound) { if (!PlayerHolder.isBound) {
playerHolder.startService( PlayerHolder.startService(
false, this@VideoDetailFragment false, this@VideoDetailFragment
) )
} }
@ -2472,7 +2471,7 @@ class VideoDetailFragment :
if (currentWorker != null) { if (currentWorker != null) {
currentWorker!!.dispose() currentWorker!!.dispose()
} }
playerHolder.stopService() PlayerHolder.stopService()
setInitialData(0, null, "", null) setInitialData(0, null, "", null)
currentInfo = null currentInfo = null
updateOverlayData(null, null, mutableListOf<Image>()) updateOverlayData(null, null, mutableListOf<Image>())

View File

@ -252,7 +252,7 @@ public final class InfoItemDialog {
* @return the current {@link Builder} instance * @return the current {@link Builder} instance
*/ */
public Builder addEnqueueEntriesIfNeeded() { public Builder addEnqueueEntriesIfNeeded() {
final PlayerHolder holder = PlayerHolder.Companion.getInstance(); final PlayerHolder holder = PlayerHolder.INSTANCE;
if (holder.isPlayQueueReady()) { if (holder.isPlayQueueReady()) {
addEntry(StreamDialogDefaultEntry.ENQUEUE); addEntry(StreamDialogDefaultEntry.ENQUEUE);

View File

@ -22,12 +22,19 @@ import org.schabi.newpipe.player.playqueue.PlayQueue
import org.schabi.newpipe.util.NavigationHelper import org.schabi.newpipe.util.NavigationHelper
import java.util.function.Consumer import java.util.function.Consumer
class PlayerHolder private constructor() { private val DEBUG = MainActivity.DEBUG
private val TAG: String = PlayerHolder::class.java.getSimpleName()
/**
* Singleton that manages a `PlayerService`
* and can be used to control the player instance through the service.
*/
object PlayerHolder {
private var listener: PlayerServiceExtendedEventListener? = null private var listener: PlayerServiceExtendedEventListener? = null
private val serviceConnection = PlayerServiceConnection()
var isBound: Boolean = false var isBound: Boolean = false
private set private set
private var playerService: PlayerService? = null private var playerService: PlayerService? = null
private val player: Player? private val player: Player?
@ -110,7 +117,7 @@ class PlayerHolder private constructor() {
val intent = Intent(context, PlayerService::class.java) val intent = Intent(context, PlayerService::class.java)
intent.putExtra(PlayerService.SHOULD_START_FOREGROUND_EXTRA, true) intent.putExtra(PlayerService.SHOULD_START_FOREGROUND_EXTRA, true)
ContextCompat.startForegroundService(context, intent) ContextCompat.startForegroundService(context, intent)
serviceConnection.doPlayAfterConnect(playAfterConnect) PlayerServiceConnection.doPlayAfterConnect(playAfterConnect)
bind(context) bind(context)
} }
@ -126,7 +133,7 @@ class PlayerHolder private constructor() {
context.stopService(Intent(context, PlayerService::class.java)) context.stopService(Intent(context, PlayerService::class.java))
} }
internal inner class PlayerServiceConnection : ServiceConnection { internal object PlayerServiceConnection : ServiceConnection {
internal var playAfterConnect = false internal var playAfterConnect = false
/** /**
@ -185,7 +192,7 @@ class PlayerHolder private constructor() {
// BIND_AUTO_CREATE starts the service if it's not already running // BIND_AUTO_CREATE starts the service if it's not already running
this.isBound = bind(context, Context.BIND_AUTO_CREATE) this.isBound = bind(context, Context.BIND_AUTO_CREATE)
if (!this.isBound) { if (!this.isBound) {
context.unbindService(serviceConnection) context.unbindService(PlayerServiceConnection)
} }
} }
@ -201,7 +208,7 @@ class PlayerHolder private constructor() {
private fun bind(context: Context, flags: Int): Boolean { private fun bind(context: Context, flags: Int): Boolean {
val serviceIntent = Intent(context, PlayerService::class.java) val serviceIntent = Intent(context, PlayerService::class.java)
serviceIntent.setAction(PlayerService.BIND_PLAYER_HOLDER_ACTION) serviceIntent.setAction(PlayerService.BIND_PLAYER_HOLDER_ACTION)
return context.bindService(serviceIntent, serviceConnection, flags) return context.bindService(serviceIntent, PlayerServiceConnection, flags)
} }
private fun unbind(context: Context) { private fun unbind(context: Context) {
@ -210,7 +217,7 @@ class PlayerHolder private constructor() {
} }
if (this.isBound) { if (this.isBound) {
context.unbindService(serviceConnection) context.unbindService(PlayerServiceConnection)
this.isBound = false this.isBound = false
stopPlayerListener() stopPlayerListener()
playerService = null playerService = null
@ -223,18 +230,18 @@ class PlayerHolder private constructor() {
// setting the player listener will take care of calling relevant callbacks if the // setting the player listener will take care of calling relevant callbacks if the
// player in the service is (not) already active, also see playerStateListener below // player in the service is (not) already active, also see playerStateListener below
playerService?.setPlayerListener(playerStateListener) playerService?.setPlayerListener(playerStateListener)
this.player?.setFragmentListener(internalListener) this.player?.setFragmentListener(HolderPlayerServiceEventListener)
} }
private fun stopPlayerListener() { private fun stopPlayerListener() {
playerService?.setPlayerListener(null) playerService?.setPlayerListener(null)
this.player?.removeFragmentListener(internalListener) this.player?.removeFragmentListener(HolderPlayerServiceEventListener)
} }
/** /**
* This listener will be held by the players created by [PlayerService]. * This listener will be held by the players created by [PlayerService].
*/ */
private val internalListener: PlayerServiceEventListener = object : PlayerServiceEventListener { private object HolderPlayerServiceEventListener : PlayerServiceEventListener {
override fun onViewCreated() { override fun onViewCreated() {
listener?.onViewCreated() listener?.onViewCreated()
} }
@ -307,26 +314,11 @@ class PlayerHolder private constructor() {
// before setting its player to null // before setting its player to null
l.onPlayerDisconnected() l.onPlayerDisconnected()
} else { } else {
l.onPlayerConnected(player, serviceConnection.playAfterConnect) l.onPlayerConnected(player, PlayerServiceConnection.playAfterConnect)
// reset the value of playAfterConnect: if it was true before, it is now "consumed" // reset the value of playAfterConnect: if it was true before, it is now "consumed"
serviceConnection.playAfterConnect = false; PlayerServiceConnection.playAfterConnect = false
player.setFragmentListener(internalListener) player.setFragmentListener(HolderPlayerServiceEventListener)
} }
} }
} }
companion object {
private var instance: PlayerHolder? = null
@Synchronized
fun getInstance(): PlayerHolder {
if (instance == null) {
instance = PlayerHolder()
}
return instance!!
}
private val DEBUG = MainActivity.DEBUG
private val TAG: String = PlayerHolder::class.java.getSimpleName()
}
} }

View File

@ -28,10 +28,9 @@ fun StreamMenu(
) { ) {
val context = LocalContext.current val context = LocalContext.current
val streamViewModel = viewModel<StreamViewModel>() val streamViewModel = viewModel<StreamViewModel>()
val playerHolder = PlayerHolder.Companion.getInstance()
DropdownMenu(expanded = expanded, onDismissRequest = onDismissRequest) { DropdownMenu(expanded = expanded, onDismissRequest = onDismissRequest) {
if (playerHolder.isPlayQueueReady) { if (PlayerHolder.isPlayQueueReady) {
DropdownMenuItem( DropdownMenuItem(
text = { Text(text = stringResource(R.string.enqueue_stream)) }, text = { Text(text = stringResource(R.string.enqueue_stream)) },
onClick = { onClick = {
@ -42,7 +41,7 @@ fun StreamMenu(
} }
) )
if (playerHolder.queuePosition < playerHolder.queueSize - 1) { if (PlayerHolder.queuePosition < PlayerHolder.queueSize - 1) {
DropdownMenuItem( DropdownMenuItem(
text = { Text(text = stringResource(R.string.enqueue_next_stream)) }, text = { Text(text = stringResource(R.string.enqueue_next_stream)) },
onClick = { onClick = {

View File

@ -200,7 +200,7 @@ public final class NavigationHelper {
} }
public static void enqueueOnPlayer(final Context context, final PlayQueue queue) { public static void enqueueOnPlayer(final Context context, final PlayQueue queue) {
PlayerType playerType = PlayerHolder.Companion.getInstance().getType(); PlayerType playerType = PlayerHolder.INSTANCE.getType();
if (playerType == null) { if (playerType == null) {
Log.e(TAG, "Enqueueing but no player is open; defaulting to background player"); Log.e(TAG, "Enqueueing but no player is open; defaulting to background player");
playerType = PlayerType.AUDIO; playerType = PlayerType.AUDIO;
@ -211,7 +211,7 @@ public final class NavigationHelper {
/* ENQUEUE NEXT */ /* ENQUEUE NEXT */
public static void enqueueNextOnPlayer(final Context context, final PlayQueue queue) { public static void enqueueNextOnPlayer(final Context context, final PlayQueue queue) {
PlayerType playerType = PlayerHolder.Companion.getInstance().getType(); PlayerType playerType = PlayerHolder.INSTANCE.getType();
if (playerType == null) { if (playerType == null) {
Log.e(TAG, "Enqueueing next but no player is open; defaulting to background player"); Log.e(TAG, "Enqueueing next but no player is open; defaulting to background player");
playerType = PlayerType.AUDIO; playerType = PlayerType.AUDIO;
@ -421,13 +421,13 @@ public final class NavigationHelper {
final boolean switchingPlayers) { final boolean switchingPlayers) {
final boolean autoPlay; final boolean autoPlay;
@Nullable final PlayerType playerType = PlayerHolder.Companion.getInstance().getType(); @Nullable final PlayerType playerType = PlayerHolder.INSTANCE.getType();
if (playerType == null) { if (playerType == null) {
// no player open // no player open
autoPlay = PlayerHelper.isAutoplayAllowedByUser(context); autoPlay = PlayerHelper.isAutoplayAllowedByUser(context);
} else if (switchingPlayers) { } else if (switchingPlayers) {
// switching player to main player // switching player to main player
autoPlay = PlayerHolder.Companion.getInstance().isPlaying(); // keep play/pause state autoPlay = PlayerHolder.INSTANCE.isPlaying(); // keep play/pause state
} else if (playerType == PlayerType.MAIN) { } else if (playerType == PlayerType.MAIN) {
// opening new stream while already playing in main player // opening new stream while already playing in main player
autoPlay = PlayerHelper.isAutoplayAllowedByUser(context); autoPlay = PlayerHelper.isAutoplayAllowedByUser(context);