Skip to content

Commit a7a7dc5

Browse files
committed
Handle player and player service separately
This is, again, a consequence of the commit "Drop some assumptions on how PlayerService is started and reused". This commit notified VideoDetailFragment of player starting and stopping independently of the player. Read the comments in the code changes for more information.
1 parent 126f4b0 commit a7a7dc5

File tree

5 files changed

+158
-42
lines changed

5 files changed

+158
-42
lines changed

app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -236,11 +236,14 @@ public final class VideoDetailFragment
236236
// Service management
237237
//////////////////////////////////////////////////////////////////////////*/
238238
@Override
239-
public void onServiceConnected(final Player connectedPlayer,
240-
final PlayerService connectedPlayerService,
241-
final boolean playAfterConnect) {
242-
player = connectedPlayer;
239+
public void onServiceConnected(@NonNull final PlayerService connectedPlayerService) {
243240
playerService = connectedPlayerService;
241+
}
242+
243+
@Override
244+
public void onPlayerConnected(@NonNull final Player connectedPlayer,
245+
final boolean playAfterConnect) {
246+
player = connectedPlayer;
244247

245248
// It will do nothing if the player is not in fullscreen mode
246249
hideSystemUiIfNeeded();
@@ -272,11 +275,18 @@ && isAutoplayEnabled()
272275
updateOverlayPlayQueueButtonVisibility();
273276
}
274277

278+
@Override
279+
public void onPlayerDisconnected() {
280+
player = null;
281+
// the binding could be null at this point, if the app is finishing
282+
if (binding != null) {
283+
restoreDefaultBrightness();
284+
}
285+
}
286+
275287
@Override
276288
public void onServiceDisconnected() {
277289
playerService = null;
278-
player = null;
279-
restoreDefaultBrightness();
280290
}
281291

282292

app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ public void onServiceConnected(final ComponentName name, final IBinder service)
224224
Log.d(TAG, "Player service is connected");
225225

226226
if (service instanceof PlayerService.LocalBinder) {
227-
player = ((PlayerService.LocalBinder) service).getPlayer();
227+
player = ((PlayerService.LocalBinder) service).getService().getPlayer();
228228
}
229229

230230
if (player == null || player.getPlayQueue() == null || player.exoPlayerIsNull()) {

app/src/main/java/org/schabi/newpipe/player/PlayerService.java

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646

4747
import java.lang.ref.WeakReference;
4848
import java.util.List;
49+
import java.util.function.Consumer;
4950

5051

5152
/**
@@ -74,6 +75,13 @@ public final class PlayerService extends MediaBrowserServiceCompat {
7475

7576
private final IBinder mBinder = new PlayerService.LocalBinder(this);
7677

78+
/**
79+
* The parameter taken by this {@link Consumer} can be null to indicate the player is being
80+
* stopped.
81+
*/
82+
@Nullable
83+
private Consumer<Player> onPlayerStartedOrStopped = null;
84+
7785

7886
//region Service lifecycle
7987
@Override
@@ -127,7 +135,8 @@ public int onStartCommand(final Intent intent, final int flags, final int startI
127135
// PlayerService using startForegroundService(), will have SHOULD_START_FOREGROUND_EXTRA,
128136
// to ensure startForeground() is called (otherwise Android will force-crash the app).
129137
if (intent.getBooleanExtra(SHOULD_START_FOREGROUND_EXTRA, false)) {
130-
if (player == null) {
138+
final boolean playerWasNull = (player == null);
139+
if (playerWasNull) {
131140
// make sure the player exists, in case the service was resumed
132141
player = new Player(this, mediaSession, sessionConnector);
133142
}
@@ -141,6 +150,13 @@ public int onStartCommand(final Intent intent, final int flags, final int startI
141150
// shouldn't do anything.
142151
player.UIs().get(NotificationPlayerUi.class)
143152
.ifPresent(NotificationPlayerUi::createNotificationAndStartForeground);
153+
154+
if (playerWasNull && onPlayerStartedOrStopped != null) {
155+
// notify that a new player was created (but do it after creating the foreground
156+
// notification just to make sure we don't incur, due to slowness, in
157+
// "Context.startForegroundService() did not then call Service.startForeground()")
158+
onPlayerStartedOrStopped.accept(player);
159+
}
144160
}
145161

146162
if (Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction())
@@ -204,6 +220,10 @@ public void onDestroy() {
204220

205221
private void cleanup() {
206222
if (player != null) {
223+
if (onPlayerStartedOrStopped != null) {
224+
// notify that the player is being destroyed
225+
onPlayerStartedOrStopped.accept(null);
226+
}
207227
player.destroy();
208228
player = null;
209229
}
@@ -279,9 +299,31 @@ public static class LocalBinder extends Binder {
279299
public PlayerService getService() {
280300
return playerService.get();
281301
}
302+
}
282303

283-
public Player getPlayer() {
284-
return playerService.get().player;
304+
/**
305+
* @return the current active player instance. May be null, since the player service can outlive
306+
* the player e.g. to respond to Android Auto media browser queries.
307+
*/
308+
@Nullable
309+
public Player getPlayer() {
310+
return player;
311+
}
312+
313+
/**
314+
* Sets the listener that will be called when the player is started or stopped. If a
315+
* {@code null} listener is passed, then the current listener will be unset. The parameter taken
316+
* by the {@link Consumer} can be null to indicate that the player is stopping.
317+
* @param listener the listener to set or unset
318+
*/
319+
public void setPlayerListener(@Nullable final Consumer<Player> listener) {
320+
this.onPlayerStartedOrStopped = listener;
321+
if (listener != null) {
322+
if (player == null) {
323+
listener.accept(null);
324+
} else {
325+
listener.accept(player);
326+
}
285327
}
286328
}
287329
//endregion
Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,48 @@
11
package org.schabi.newpipe.player.event;
22

3+
import androidx.annotation.NonNull;
4+
35
import org.schabi.newpipe.player.PlayerService;
46
import org.schabi.newpipe.player.Player;
57

8+
/**
9+
* In addition to {@link PlayerServiceEventListener}, provides callbacks for service and player
10+
* connections and disconnections. "Connected" here means that the service (resp. the
11+
* player) is running and is bound to {@link org.schabi.newpipe.player.helper.PlayerHolder}.
12+
* "Disconnected" means that either the service (resp. the player) was stopped completely, or that
13+
* {@link org.schabi.newpipe.player.helper.PlayerHolder} is not bound.
14+
*/
615
public interface PlayerServiceExtendedEventListener extends PlayerServiceEventListener {
7-
void onServiceConnected(Player player,
8-
PlayerService playerService,
9-
boolean playAfterConnect);
16+
/**
17+
* The player service just connected to {@link org.schabi.newpipe.player.helper.PlayerHolder},
18+
* but the player may not be active at this moment, e.g. in case the service is running to
19+
* respond to Android Auto media browser queries without playing anything.
20+
* {@link #onPlayerConnected(Player, boolean)} will be called right after this function if there
21+
* is a player.
22+
*
23+
* @param playerService the newly connected player service
24+
*/
25+
void onServiceConnected(@NonNull PlayerService playerService);
26+
27+
/**
28+
* The player service is already connected and the player was just started.
29+
*
30+
* @param player the newly connected or started player
31+
* @param playAfterConnect whether to open the video player in the video details fragment
32+
*/
33+
void onPlayerConnected(@NonNull Player player, boolean playAfterConnect);
34+
35+
/**
36+
* The player got disconnected, for one of these reasons: the player is getting closed while
37+
* leaving the service open for future media browser queries, the service is stopping
38+
* completely, or {@link org.schabi.newpipe.player.helper.PlayerHolder} is unbinding.
39+
*/
40+
void onPlayerDisconnected();
41+
42+
/**
43+
* The service got disconnected from {@link org.schabi.newpipe.player.helper.PlayerHolder},
44+
* either because {@link org.schabi.newpipe.player.helper.PlayerHolder} is unbinding or because
45+
* the service is stopping completely.
46+
*/
1047
void onServiceDisconnected();
1148
}

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

Lines changed: 56 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@
2424
import org.schabi.newpipe.player.playqueue.PlayQueue;
2525
import org.schabi.newpipe.util.NavigationHelper;
2626

27+
import java.util.Optional;
28+
import java.util.function.Consumer;
29+
2730
public final class PlayerHolder {
2831

2932
private PlayerHolder() {
@@ -45,7 +48,16 @@ public static synchronized PlayerHolder getInstance() {
4548
private final PlayerServiceConnection serviceConnection = new PlayerServiceConnection();
4649
private boolean bound;
4750
@Nullable private PlayerService playerService;
48-
@Nullable private Player player;
51+
52+
private Optional<Player> getPlayer() {
53+
return Optional.ofNullable(playerService)
54+
.flatMap(s -> Optional.ofNullable(s.getPlayer()));
55+
}
56+
57+
private Optional<PlayQueue> getPlayQueue() {
58+
// player play queue might be null e.g. while player is starting
59+
return getPlayer().flatMap(p -> Optional.ofNullable(p.getPlayQueue()));
60+
}
4961

5062
/**
5163
* Returns the current {@link PlayerType} of the {@link PlayerService} service,
@@ -55,21 +67,15 @@ public static synchronized PlayerHolder getInstance() {
5567
*/
5668
@Nullable
5769
public PlayerType getType() {
58-
if (player == null) {
59-
return null;
60-
}
61-
return player.getPlayerType();
70+
return getPlayer().map(Player::getPlayerType).orElse(null);
6271
}
6372

6473
public boolean isPlaying() {
65-
if (player == null) {
66-
return false;
67-
}
68-
return player.isPlaying();
74+
return getPlayer().map(Player::isPlaying).orElse(false);
6975
}
7076

7177
public boolean isPlayerOpen() {
72-
return player != null;
78+
return getPlayer().isPresent();
7379
}
7480

7581
/**
@@ -78,26 +84,19 @@ public boolean isPlayerOpen() {
7884
* @return true only if the player is open and its play queue is ready (i.e. it is not null)
7985
*/
8086
public boolean isPlayQueueReady() {
81-
return player != null && player.getPlayQueue() != null;
87+
return getPlayQueue().isPresent();
8288
}
8389

8490
public boolean isBound() {
8591
return bound;
8692
}
8793

8894
public int getQueueSize() {
89-
if (player == null || player.getPlayQueue() == null) {
90-
// player play queue might be null e.g. while player is starting
91-
return 0;
92-
}
93-
return player.getPlayQueue().size();
95+
return getPlayQueue().map(PlayQueue::size).orElse(0);
9496
}
9597

9698
public int getQueuePosition() {
97-
if (player == null || player.getPlayQueue() == null) {
98-
return 0;
99-
}
100-
return player.getPlayQueue().getIndex();
99+
return getPlayQueue().map(PlayQueue::getIndex).orElse(0);
101100
}
102101

103102
public void setListener(@Nullable final PlayerServiceExtendedEventListener newListener) {
@@ -108,9 +107,10 @@ public void setListener(@Nullable final PlayerServiceExtendedEventListener newLi
108107
}
109108

110109
// Force reload data from service
111-
if (player != null) {
112-
listener.onServiceConnected(player, playerService, false);
110+
if (playerService != null) {
111+
listener.onServiceConnected(playerService);
113112
startPlayerListener();
113+
// ^ will call listener.onPlayerConnected() down the line if there is an active player
114114
}
115115
}
116116

@@ -181,11 +181,12 @@ public void onServiceConnected(final ComponentName compName, final IBinder servi
181181
final PlayerService.LocalBinder localBinder = (PlayerService.LocalBinder) service;
182182

183183
playerService = localBinder.getService();
184-
player = localBinder.getPlayer();
185184
if (listener != null) {
186-
listener.onServiceConnected(player, playerService, playAfterConnect);
185+
listener.onServiceConnected(playerService);
186+
getPlayer().ifPresent(p -> listener.onPlayerConnected(p, playAfterConnect));
187187
}
188188
startPlayerListener();
189+
// ^ will call listener.onPlayerConnected() down the line if there is an active player
189190

190191
// notify the main activity that binding the service has completed, so that it can
191192
// open the bottom mini-player
@@ -229,25 +230,32 @@ private void unbind(final Context context) {
229230
bound = false;
230231
stopPlayerListener();
231232
playerService = null;
232-
player = null;
233233
if (listener != null) {
234+
listener.onPlayerDisconnected();
234235
listener.onServiceDisconnected();
235236
}
236237
}
237238
}
238239

239240
private void startPlayerListener() {
240-
if (player != null) {
241-
player.setFragmentListener(internalListener);
241+
if (playerService != null) {
242+
// setting the player listener will take care of calling relevant callbacks if the
243+
// player in the service is (not) already active, also see playerStateListener below
244+
playerService.setPlayerListener(playerStateListener);
242245
}
246+
getPlayer().ifPresent(p -> p.setFragmentListener(internalListener));
243247
}
244248

245249
private void stopPlayerListener() {
246-
if (player != null) {
247-
player.removeFragmentListener(internalListener);
250+
if (playerService != null) {
251+
playerService.setPlayerListener(null);
248252
}
253+
getPlayer().ifPresent(p -> p.removeFragmentListener(internalListener));
249254
}
250255

256+
/**
257+
* This listener will be held by the players created by {@link PlayerService}.
258+
*/
251259
private final PlayerServiceEventListener internalListener =
252260
new PlayerServiceEventListener() {
253261
@Override
@@ -334,4 +342,23 @@ public void onServiceStopped() {
334342
unbind(getCommonContext());
335343
}
336344
};
345+
346+
/**
347+
* This listener will be held by bound {@link PlayerService}s to notify of the player starting
348+
* or stopping. This is necessary since the service outlives the player e.g. to answer Android
349+
* Auto media browser queries.
350+
*/
351+
private final Consumer<Player> playerStateListener = (@Nullable final Player player) -> {
352+
if (listener != null) {
353+
if (player == null) {
354+
// player.fragmentListener=null is already done by player.stopActivityBinding(),
355+
// which is called by player.destroy(), which is in turn called by PlayerService
356+
// before setting its player to null
357+
listener.onPlayerDisconnected();
358+
} else {
359+
listener.onPlayerConnected(player, serviceConnection.playAfterConnect);
360+
player.setFragmentListener(internalListener);
361+
}
362+
}
363+
};
337364
}

0 commit comments

Comments
 (0)