mirror of
				https://github.com/TeamNewPipe/NewPipe
				synced 2025-10-31 15:23:00 +00:00 
			
		
		
		
	convert PlayerHolder to Singleton, handle context within, bugfix ServiceConnection leak
- bugfix: have ServiceConnection created only once! - select the context within the PlayerHolder to start, stop, bind or unbind the service -> we have to make sure the Service is started AND stopped within the same context -> so let PlayerHolder be the one to select the context - remove removeListener() and replace the call with setListener(null) - Compatibility: use ContextCompat.startForegroundService instead of startService()
This commit is contained in:
		| @@ -823,7 +823,7 @@ public class MainActivity extends AppCompatActivity { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         if (PlayerHolder.isPlayerOpen()) { | ||||
|         if (PlayerHolder.getInstance().isPlayerOpen()) { | ||||
|             // if the player is already open, no need for a broadcast receiver | ||||
|             openMiniPlayerIfMissing(); | ||||
|         } else { | ||||
|   | ||||
| @@ -453,7 +453,7 @@ public class RouterActivity extends AppCompatActivity { | ||||
|                 returnList.add(showInfo); | ||||
|                 returnList.add(videoPlayer); | ||||
|             } else { | ||||
|                 final MainPlayer.PlayerType playerType = PlayerHolder.getType(); | ||||
|                 final MainPlayer.PlayerType playerType = PlayerHolder.getInstance().getType(); | ||||
|                 if (capabilities.contains(VIDEO) | ||||
|                         && PlayerHelper.isAutoplayAllowedByUser(context) | ||||
|                         && playerType == null || playerType == MainPlayer.PlayerType.VIDEO) { | ||||
|   | ||||
| @@ -201,6 +201,7 @@ public final class VideoDetailFragment | ||||
|     @Nullable | ||||
|     private MainPlayer playerService; | ||||
|     private Player player; | ||||
|     private PlayerHolder playerHolder = PlayerHolder.getInstance(); | ||||
|  | ||||
|     /*////////////////////////////////////////////////////////////////////////// | ||||
|     // Service management | ||||
| @@ -360,9 +361,9 @@ public final class VideoDetailFragment | ||||
|         // Stop the service when user leaves the app with double back press | ||||
|         // if video player is selected. Otherwise unbind | ||||
|         if (activity.isFinishing() && isPlayerAvailable() && player.videoPlayerSelected()) { | ||||
|             PlayerHolder.stopService(App.getApp()); | ||||
|             playerHolder.stopService(); | ||||
|         } else { | ||||
|             PlayerHolder.removeListener(); | ||||
|             playerHolder.setListener(null); | ||||
|         } | ||||
|  | ||||
|         PreferenceManager.getDefaultSharedPreferences(activity) | ||||
| @@ -660,10 +661,10 @@ public final class VideoDetailFragment | ||||
|         }); | ||||
|  | ||||
|         setupBottomPlayer(); | ||||
|         if (!PlayerHolder.bound) { | ||||
|         if (!playerHolder.bound) { | ||||
|             setHeightThumbnail(); | ||||
|         } else { | ||||
|             PlayerHolder.startService(App.getApp(), false, this); | ||||
|             playerHolder.startService(false, this); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -1097,7 +1098,7 @@ public final class VideoDetailFragment | ||||
|  | ||||
|         // See UI changes while remote playQueue changes | ||||
|         if (!isPlayerAvailable()) { | ||||
|             PlayerHolder.startService(App.getApp(), false, this); | ||||
|             playerHolder.startService(false, this); | ||||
|         } | ||||
|  | ||||
|         toggleFullscreenIfInFullscreenMode(); | ||||
| @@ -1123,7 +1124,7 @@ public final class VideoDetailFragment | ||||
|     private void openNormalBackgroundPlayer(final boolean append) { | ||||
|         // See UI changes while remote playQueue changes | ||||
|         if (!isPlayerAvailable()) { | ||||
|             PlayerHolder.startService(App.getApp(), false, this); | ||||
|             playerHolder.startService(false, this); | ||||
|         } | ||||
|  | ||||
|         final PlayQueue queue = setupPlayQueueForIntent(append); | ||||
| @@ -1137,7 +1138,7 @@ public final class VideoDetailFragment | ||||
|  | ||||
|     private void openMainPlayer() { | ||||
|         if (!isPlayerServiceAvailable()) { | ||||
|             PlayerHolder.startService(App.getApp(), autoPlayEnabled, this); | ||||
|             playerHolder.startService(autoPlayEnabled, this); | ||||
|             return; | ||||
|         } | ||||
|         if (currentInfo == null) { | ||||
| @@ -1155,7 +1156,7 @@ public final class VideoDetailFragment | ||||
|  | ||||
|         final Intent playerIntent = NavigationHelper | ||||
|                 .getPlayerIntent(requireContext(), MainPlayer.class, queue, true, autoPlayEnabled); | ||||
|         activity.startService(playerIntent); | ||||
|         ContextCompat.startForegroundService(activity, playerIntent); | ||||
|     } | ||||
|  | ||||
|     private void hideMainPlayer() { | ||||
| @@ -1373,9 +1374,9 @@ public final class VideoDetailFragment | ||||
|                             bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED); | ||||
|                         } | ||||
|                         // Rebound to the service if it was closed via notification or mini player | ||||
|                         if (!PlayerHolder.bound) { | ||||
|                             PlayerHolder.startService( | ||||
|                                     App.getApp(), false, VideoDetailFragment.this); | ||||
|                         if (!playerHolder.bound) { | ||||
|                             playerHolder.startService( | ||||
|                                     false, VideoDetailFragment.this); | ||||
|                         } | ||||
|                         break; | ||||
|                 } | ||||
| @@ -2119,7 +2120,7 @@ public final class VideoDetailFragment | ||||
|         if (currentWorker != null) { | ||||
|             currentWorker.dispose(); | ||||
|         } | ||||
|         PlayerHolder.stopService(App.getApp()); | ||||
|         playerHolder.stopService(); | ||||
|         setInitialData(0, null, "", null); | ||||
|         currentInfo = null; | ||||
|         updateOverlayData(null, null, null); | ||||
|   | ||||
| @@ -353,7 +353,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> | ||||
|  | ||||
|         final List<StreamDialogEntry> entries = new ArrayList<>(); | ||||
|  | ||||
|         if (PlayerHolder.getType() != null) { | ||||
|         if (PlayerHolder.getInstance().getType() != null) { | ||||
|             entries.add(StreamDialogEntry.enqueue); | ||||
|         } | ||||
|         if (item.getStreamType() == StreamType.AUDIO_STREAM) { | ||||
|   | ||||
| @@ -144,7 +144,7 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> { | ||||
|  | ||||
|         final ArrayList<StreamDialogEntry> entries = new ArrayList<>(); | ||||
|  | ||||
|         if (PlayerHolder.getType() != null) { | ||||
|         if (PlayerHolder.getInstance().getType() != null) { | ||||
|             entries.add(StreamDialogEntry.enqueue); | ||||
|         } | ||||
|         if (item.getStreamType() == StreamType.AUDIO_STREAM) { | ||||
|   | ||||
| @@ -331,7 +331,7 @@ class FeedFragment : BaseStateFragment<FeedState>() { | ||||
|         if (context == null || context.resources == null || activity == null) return | ||||
|  | ||||
|         val entries = ArrayList<StreamDialogEntry>() | ||||
|         if (PlayerHolder.getType() != null) { | ||||
|         if (PlayerHolder.getInstance().getType() != null) { | ||||
|             entries.add(StreamDialogEntry.enqueue) | ||||
|         } | ||||
|         if (item.streamType == StreamType.AUDIO_STREAM) { | ||||
|   | ||||
| @@ -340,7 +340,7 @@ public class StatisticsPlaylistFragment | ||||
|  | ||||
|         final ArrayList<StreamDialogEntry> entries = new ArrayList<>(); | ||||
|  | ||||
|         if (PlayerHolder.getType() != null) { | ||||
|         if (PlayerHolder.getInstance().getType() != null) { | ||||
|             entries.add(StreamDialogEntry.enqueue); | ||||
|         } | ||||
|         if (infoItem.getStreamType() == StreamType.AUDIO_STREAM) { | ||||
|   | ||||
| @@ -749,7 +749,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt | ||||
|  | ||||
|         final ArrayList<StreamDialogEntry> entries = new ArrayList<>(); | ||||
|  | ||||
|         if (PlayerHolder.getType() != null) { | ||||
|         if (PlayerHolder.getInstance().getType() != null) { | ||||
|             entries.add(StreamDialogEntry.enqueue); | ||||
|         } | ||||
|         if (infoItem.getStreamType() == StreamType.AUDIO_STREAM) { | ||||
|   | ||||
| @@ -8,6 +8,7 @@ import android.os.IBinder; | ||||
| import android.util.Log; | ||||
|  | ||||
| import androidx.annotation.Nullable; | ||||
| import androidx.core.content.ContextCompat; | ||||
|  | ||||
| import com.google.android.exoplayer2.ExoPlaybackException; | ||||
| import com.google.android.exoplayer2.PlaybackParameters; | ||||
| @@ -22,18 +23,27 @@ import org.schabi.newpipe.player.event.PlayerServiceExtendedEventListener; | ||||
| import org.schabi.newpipe.player.playqueue.PlayQueue; | ||||
|  | ||||
| public final class PlayerHolder { | ||||
|  | ||||
|     private PlayerHolder() { | ||||
|     } | ||||
|  | ||||
|     private static final boolean DEBUG = MainActivity.DEBUG; | ||||
|     private static final String TAG = "PlayerHolder"; | ||||
|     private static PlayerHolder instance; | ||||
|     public static synchronized PlayerHolder getInstance() { | ||||
|         if (PlayerHolder.instance == null) { | ||||
|             PlayerHolder.instance = new PlayerHolder(); | ||||
|         } | ||||
|         return PlayerHolder.instance; | ||||
|     } | ||||
|  | ||||
|     private static PlayerServiceExtendedEventListener listener; | ||||
|     private final boolean DEBUG = MainActivity.DEBUG; | ||||
|     private final String TAG = PlayerHolder.class.getSimpleName(); | ||||
|  | ||||
|     private static ServiceConnection serviceConnection; | ||||
|     public static boolean bound; | ||||
|     private static MainPlayer playerService; | ||||
|     private static Player player; | ||||
|     private PlayerServiceExtendedEventListener listener; | ||||
|  | ||||
|     private final PlayerServiceConnection serviceConnection = new PlayerServiceConnection(); | ||||
|     public boolean bound; | ||||
|     private MainPlayer playerService; | ||||
|     private Player player; | ||||
|  | ||||
|     /** | ||||
|      * Returns the current {@link MainPlayer.PlayerType} of the {@link MainPlayer} service, | ||||
| @@ -42,26 +52,31 @@ public final class PlayerHolder { | ||||
|      * @return Current PlayerType | ||||
|      */ | ||||
|     @Nullable | ||||
|     public static MainPlayer.PlayerType getType() { | ||||
|     public MainPlayer.PlayerType getType() { | ||||
|         if (player == null) { | ||||
|             return null; | ||||
|         } | ||||
|         return player.getPlayerType(); | ||||
|     } | ||||
|  | ||||
|     public static boolean isPlaying() { | ||||
|     public boolean isPlaying() { | ||||
|         if (player == null) { | ||||
|             return false; | ||||
|         } | ||||
|         return player.isPlaying(); | ||||
|     } | ||||
|  | ||||
|     public static boolean isPlayerOpen() { | ||||
|     public boolean isPlayerOpen() { | ||||
|         return player != null; | ||||
|     } | ||||
|  | ||||
|     public static void setListener(final PlayerServiceExtendedEventListener newListener) { | ||||
|     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); | ||||
| @@ -69,14 +84,15 @@ public final class PlayerHolder { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public static void removeListener() { | ||||
|         listener = null; | ||||
|     // 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 static void startService(final Context context, | ||||
|                                     final boolean playAfterConnect, | ||||
|                                     final PlayerServiceExtendedEventListener newListener) { | ||||
|     public void startService(final boolean playAfterConnect, | ||||
|                              final PlayerServiceExtendedEventListener newListener) { | ||||
|         final Context context = getCommonContext(); | ||||
|         setListener(newListener); | ||||
|         if (bound) { | ||||
|             return; | ||||
| @@ -85,58 +101,65 @@ public final class PlayerHolder { | ||||
|         // and NullPointerExceptions inside the service because the service will be | ||||
|         // bound twice. Prevent it with unbinding first | ||||
|         unbind(context); | ||||
|         context.startService(new Intent(context, MainPlayer.class)); | ||||
|         serviceConnection = getServiceConnection(context, playAfterConnect); | ||||
|         ContextCompat.startForegroundService(context, new Intent(context, MainPlayer.class)); | ||||
|         serviceConnection.doPlayAfterConnect(playAfterConnect); | ||||
|         bind(context); | ||||
|     } | ||||
|  | ||||
|     public static void stopService(final Context context) { | ||||
|     public void stopService() { | ||||
|         final Context context = getCommonContext(); | ||||
|         unbind(context); | ||||
|         context.stopService(new Intent(context, MainPlayer.class)); | ||||
|     } | ||||
|  | ||||
|     private static ServiceConnection getServiceConnection(final Context context, | ||||
|                                                           final boolean playAfterConnect) { | ||||
|         return new ServiceConnection() { | ||||
|             @Override | ||||
|             public void onServiceDisconnected(final ComponentName compName) { | ||||
|                 if (DEBUG) { | ||||
|                     Log.d(TAG, "Player service is disconnected"); | ||||
|                 } | ||||
|     class PlayerServiceConnection implements ServiceConnection { | ||||
|  | ||||
|                 unbind(context); | ||||
|         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"); | ||||
|             } | ||||
|  | ||||
|             @Override | ||||
|             public void onServiceConnected(final ComponentName compName, final IBinder service) { | ||||
|                 if (DEBUG) { | ||||
|                     Log.d(TAG, "Player service is connected"); | ||||
|                 } | ||||
|                 final MainPlayer.LocalBinder localBinder = (MainPlayer.LocalBinder) service; | ||||
|             final Context context = getCommonContext(); | ||||
|             unbind(context); | ||||
|         } | ||||
|  | ||||
|                 playerService = localBinder.getService(); | ||||
|                 player = localBinder.getPlayer(); | ||||
|                 if (listener != null) { | ||||
|                     listener.onServiceConnected(player, playerService, playAfterConnect); | ||||
|                 } | ||||
|                 startPlayerListener(); | ||||
|         @Override | ||||
|         public void onServiceConnected(final ComponentName compName, final IBinder service) { | ||||
|             if (DEBUG) { | ||||
|                 Log.d(TAG, "Player service is connected"); | ||||
|             } | ||||
|         }; | ||||
|     } | ||||
|             final MainPlayer.LocalBinder localBinder = (MainPlayer.LocalBinder) service; | ||||
|  | ||||
|     private static void bind(final Context context) { | ||||
|             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"); | ||||
|         } | ||||
|  | ||||
|         final Intent serviceIntent = new Intent(context, MainPlayer.class); | ||||
|         bound = context.bindService(serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE); | ||||
|         bound = context.bindService(serviceIntent, serviceConnection, | ||||
|                 Context.BIND_AUTO_CREATE); | ||||
|         if (!bound) { | ||||
|             context.unbindService(serviceConnection); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private static void unbind(final Context context) { | ||||
|     private void unbind(final Context context) { | ||||
|         if (DEBUG) { | ||||
|             Log.d(TAG, "unbind() called"); | ||||
|         } | ||||
| @@ -153,21 +176,19 @@ public final class PlayerHolder { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|     private static void startPlayerListener() { | ||||
|     private void startPlayerListener() { | ||||
|         if (player != null) { | ||||
|             player.setFragmentListener(INNER_LISTENER); | ||||
|             player.setFragmentListener(internalListener); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private static void stopPlayerListener() { | ||||
|     private void stopPlayerListener() { | ||||
|         if (player != null) { | ||||
|             player.removeFragmentListener(INNER_LISTENER); | ||||
|             player.removeFragmentListener(internalListener); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|     private static final PlayerServiceEventListener INNER_LISTENER = | ||||
|     private final PlayerServiceEventListener internalListener = | ||||
|             new PlayerServiceEventListener() { | ||||
|                 @Override | ||||
|                 public void onFullscreenStateChanged(final boolean fullscreen) { | ||||
| @@ -242,7 +263,7 @@ public final class PlayerHolder { | ||||
|                     if (listener != null) { | ||||
|                         listener.onServiceStopped(); | ||||
|                     } | ||||
|                     unbind(App.getApp()); | ||||
|                     unbind(getCommonContext()); | ||||
|                 } | ||||
|             }; | ||||
| } | ||||
|   | ||||
| @@ -349,13 +349,13 @@ public final class NavigationHelper { | ||||
|                                                final boolean switchingPlayers) { | ||||
|  | ||||
|         final boolean autoPlay; | ||||
|         @Nullable final MainPlayer.PlayerType playerType = PlayerHolder.getType(); | ||||
|         @Nullable final MainPlayer.PlayerType playerType = PlayerHolder.getInstance().getType(); | ||||
|         if (playerType == null) { | ||||
|             // no player open | ||||
|             autoPlay = PlayerHelper.isAutoplayAllowedByUser(context); | ||||
|         } else if (switchingPlayers) { | ||||
|             // switching player to main player | ||||
|             autoPlay = PlayerHolder.isPlaying(); // keep play/pause state | ||||
|             autoPlay = PlayerHolder.getInstance().isPlaying(); // keep play/pause state | ||||
|         } else if (playerType == MainPlayer.PlayerType.VIDEO) { | ||||
|             // opening new stream while already playing in main player | ||||
|             autoPlay = PlayerHelper.isAutoplayAllowedByUser(context); | ||||
|   | ||||
| @@ -39,7 +39,7 @@ public enum StreamDialogEntry { | ||||
|      * Info: Add this entry within showStreamDialog. | ||||
|      */ | ||||
|     enqueue(R.string.enqueue_stream, (fragment, item) -> { | ||||
|         final MainPlayer.PlayerType type = PlayerHolder.getType(); | ||||
|         final MainPlayer.PlayerType type = PlayerHolder.getInstance().getType(); | ||||
|  | ||||
|         if (type == AUDIO) { | ||||
|             NavigationHelper.enqueueOnBackgroundPlayer(fragment.getContext(), | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 evermind
					evermind