17
17
package androidx .media3 .session ;
18
18
19
19
import static androidx .media3 .test .session .common .TestUtils .TIMEOUT_MS ;
20
+ import static androidx .media3 .test .session .common .TestUtils .getEventsAsList ;
20
21
import static androidx .test .platform .app .InstrumentationRegistry .getInstrumentation ;
21
22
import static com .google .common .truth .Truth .assertThat ;
22
23
import static java .util .concurrent .TimeUnit .MILLISECONDS ;
24
+ import static org .junit .Assert .assertThrows ;
23
25
24
26
import android .net .Uri ;
25
27
import android .os .Bundle ;
26
28
import android .os .Handler ;
29
+ import android .os .Looper ;
30
+ import android .support .v4 .media .MediaDescriptionCompat ;
27
31
import android .support .v4 .media .session .MediaControllerCompat ;
32
+ import android .support .v4 .media .session .MediaSessionCompat ;
28
33
import android .support .v4 .media .session .PlaybackStateCompat ;
29
34
import androidx .annotation .Nullable ;
35
+ import androidx .core .util .Predicate ;
30
36
import androidx .media3 .common .C ;
31
37
import androidx .media3 .common .ForwardingPlayer ;
32
38
import androidx .media3 .common .MediaItem ;
33
39
import androidx .media3 .common .PlaybackParameters ;
34
40
import androidx .media3 .common .Player ;
41
+ import androidx .media3 .common .SimpleBasePlayer ;
35
42
import androidx .media3 .common .Timeline ;
36
43
import androidx .media3 .common .util .ConditionVariable ;
37
44
import androidx .media3 .common .util .Consumer ;
@@ -1261,6 +1268,173 @@ public void onRepeatModeChanged(int repeatMode) {
1261
1268
releasePlayer (player );
1262
1269
}
1263
1270
1271
+ @ Test
1272
+ public void playerWithCommandChangeMediaItems_flagHandleQueueIsAdvertised () throws Exception {
1273
+ Player player =
1274
+ createPlayerWithAvailableCommand (createDefaultPlayer (), Player .COMMAND_CHANGE_MEDIA_ITEMS );
1275
+ MediaSession mediaSession =
1276
+ createMediaSession (
1277
+ player ,
1278
+ new MediaSession .Callback () {
1279
+ @ Override
1280
+ public ListenableFuture <List <MediaItem >> onAddMediaItems (
1281
+ MediaSession mediaSession ,
1282
+ MediaSession .ControllerInfo controller ,
1283
+ List <MediaItem > mediaItems ) {
1284
+ return Futures .immediateFuture (
1285
+ ImmutableList .of (MediaItem .fromUri ("asset://media/wav/sample.wav" )));
1286
+ }
1287
+ });
1288
+ MediaControllerCompat controllerCompat = createMediaControllerCompat (mediaSession );
1289
+
1290
+ // Wait until a playback state is sent to the controller.
1291
+ getFirstPlaybackState (controllerCompat , threadTestRule .getHandler ());
1292
+ assertThat (controllerCompat .getFlags () & MediaSessionCompat .FLAG_HANDLES_QUEUE_COMMANDS )
1293
+ .isNotEqualTo (0 );
1294
+
1295
+ ArrayList <Timeline > receivedTimelines = new ArrayList <>();
1296
+ ArrayList <Integer > receivedTimelineReasons = new ArrayList <>();
1297
+ CountDownLatch latch = new CountDownLatch (2 );
1298
+ Player .Listener listener =
1299
+ new Player .Listener () {
1300
+ @ Override
1301
+ public void onTimelineChanged (
1302
+ Timeline timeline , @ Player .TimelineChangeReason int reason ) {
1303
+ receivedTimelines .add (timeline );
1304
+ receivedTimelineReasons .add (reason );
1305
+ latch .countDown ();
1306
+ }
1307
+ };
1308
+ player .addListener (listener );
1309
+
1310
+ controllerCompat .addQueueItem (
1311
+ new MediaDescriptionCompat .Builder ().setMediaId ("mediaId" ).build ());
1312
+ controllerCompat .addQueueItem (
1313
+ new MediaDescriptionCompat .Builder ().setMediaId ("mediaId" ).build (), /* index= */ 0 );
1314
+
1315
+ assertThat (latch .await (TIMEOUT_MS , MILLISECONDS )).isTrue ();
1316
+ assertThat (receivedTimelines ).hasSize (2 );
1317
+ assertThat (receivedTimelines .get (0 ).getWindowCount ()).isEqualTo (1 );
1318
+ assertThat (receivedTimelines .get (1 ).getWindowCount ()).isEqualTo (2 );
1319
+ assertThat (receivedTimelineReasons )
1320
+ .containsExactly (
1321
+ Player .TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED ,
1322
+ Player .TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED );
1323
+
1324
+ mediaSession .release ();
1325
+ releasePlayer (player );
1326
+ }
1327
+
1328
+ @ Test
1329
+ public void playerWithoutCommandChangeMediaItems_flagHandleQueueNotAdvertised () throws Exception {
1330
+ Player player =
1331
+ createPlayerWithExcludedCommand (createDefaultPlayer (), Player .COMMAND_CHANGE_MEDIA_ITEMS );
1332
+ MediaSession mediaSession =
1333
+ createMediaSession (
1334
+ player ,
1335
+ new MediaSession .Callback () {
1336
+ @ Override
1337
+ public ListenableFuture <List <MediaItem >> onAddMediaItems (
1338
+ MediaSession mediaSession ,
1339
+ MediaSession .ControllerInfo controller ,
1340
+ List <MediaItem > mediaItems ) {
1341
+ return Futures .immediateFuture (
1342
+ ImmutableList .of (MediaItem .fromUri ("asset://media/wav/sample.wav" )));
1343
+ }
1344
+ });
1345
+ MediaControllerCompat controllerCompat = createMediaControllerCompat (mediaSession );
1346
+
1347
+ // Wait until a playback state is sent to the controller.
1348
+ getFirstPlaybackState (controllerCompat , threadTestRule .getHandler ());
1349
+ assertThat (controllerCompat .getFlags () & MediaSessionCompat .FLAG_HANDLES_QUEUE_COMMANDS )
1350
+ .isEqualTo (0 );
1351
+ assertThrows (
1352
+ UnsupportedOperationException .class ,
1353
+ () ->
1354
+ controllerCompat .addQueueItem (
1355
+ new MediaDescriptionCompat .Builder ().setMediaId ("mediaId" ).build ()));
1356
+ assertThrows (
1357
+ UnsupportedOperationException .class ,
1358
+ () ->
1359
+ controllerCompat .addQueueItem (
1360
+ new MediaDescriptionCompat .Builder ().setMediaId ("mediaId" ).build (),
1361
+ /* index= */ 0 ));
1362
+
1363
+ mediaSession .release ();
1364
+ releasePlayer (player );
1365
+ }
1366
+
1367
+ @ Test
1368
+ public void playerChangesAvailableCommands_actionsAreUpdated () throws Exception {
1369
+ // TODO(b/261158047): Add COMMAND_RELEASE to the available commands so that we can release the
1370
+ // player.
1371
+ ControllingCommandsPlayer player =
1372
+ new ControllingCommandsPlayer (
1373
+ Player .Commands .EMPTY , threadTestRule .getHandler ().getLooper ());
1374
+ MediaSession mediaSession = createMediaSession (player );
1375
+ MediaControllerCompat controllerCompat = createMediaControllerCompat (mediaSession );
1376
+ LinkedBlockingDeque <PlaybackStateCompat > receivedPlaybackStateCompats =
1377
+ new LinkedBlockingDeque <>();
1378
+ MediaControllerCompat .Callback callback =
1379
+ new MediaControllerCompat .Callback () {
1380
+ @ Override
1381
+ public void onPlaybackStateChanged (PlaybackStateCompat state ) {
1382
+ receivedPlaybackStateCompats .add (state );
1383
+ }
1384
+ };
1385
+ controllerCompat .registerCallback (callback , threadTestRule .getHandler ());
1386
+
1387
+ ArrayList <Player .Events > receivedEvents = new ArrayList <>();
1388
+ ConditionVariable eventsArrived = new ConditionVariable ();
1389
+ player .addListener (
1390
+ new Player .Listener () {
1391
+ @ Override
1392
+ public void onEvents (Player player , Player .Events events ) {
1393
+ receivedEvents .add (events );
1394
+ eventsArrived .open ();
1395
+ }
1396
+ });
1397
+ threadTestRule
1398
+ .getHandler ()
1399
+ .postAndSync (
1400
+ () -> {
1401
+ player .setAvailableCommands (
1402
+ new Player .Commands .Builder ().add (Player .COMMAND_PREPARE ).build ());
1403
+ });
1404
+
1405
+ assertThat (eventsArrived .block (TIMEOUT_MS )).isTrue ();
1406
+ assertThat (getEventsAsList (receivedEvents .get (0 )))
1407
+ .containsExactly (Player .EVENT_AVAILABLE_COMMANDS_CHANGED );
1408
+ assertThat (
1409
+ waitUntilPlaybackStateArrived (
1410
+ receivedPlaybackStateCompats ,
1411
+ /* predicate= */ playbackStateCompat ->
1412
+ (playbackStateCompat .getActions () & PlaybackStateCompat .ACTION_PREPARE ) != 0 ))
1413
+ .isTrue ();
1414
+
1415
+ eventsArrived .open ();
1416
+ threadTestRule
1417
+ .getHandler ()
1418
+ .postAndSync (
1419
+ () -> {
1420
+ player .setAvailableCommands (Player .Commands .EMPTY );
1421
+ });
1422
+
1423
+ assertThat (eventsArrived .block (TIMEOUT_MS )).isTrue ();
1424
+ assertThat (
1425
+ waitUntilPlaybackStateArrived (
1426
+ receivedPlaybackStateCompats ,
1427
+ /* predicate= */ playbackStateCompat ->
1428
+ (playbackStateCompat .getActions () & PlaybackStateCompat .ACTION_PREPARE ) == 0 ))
1429
+ .isTrue ();
1430
+ assertThat (getEventsAsList (receivedEvents .get (1 )))
1431
+ .containsExactly (Player .EVENT_AVAILABLE_COMMANDS_CHANGED );
1432
+
1433
+ mediaSession .release ();
1434
+ // This player is instantiated to use the threadTestRule, so it's released on that thread.
1435
+ threadTestRule .getHandler ().postAndSync (player ::release );
1436
+ }
1437
+
1264
1438
private PlaybackStateCompat getFirstPlaybackState (
1265
1439
MediaControllerCompat mediaControllerCompat , Handler handler ) throws InterruptedException {
1266
1440
LinkedBlockingDeque <PlaybackStateCompat > playbackStateCompats = new LinkedBlockingDeque <>();
@@ -1347,6 +1521,21 @@ private static Player createPlayerWithExcludedCommand(
1347
1521
player , Player .Commands .EMPTY , new Player .Commands .Builder ().add (excludedCommand ).build ());
1348
1522
}
1349
1523
1524
+ private static boolean waitUntilPlaybackStateArrived (
1525
+ LinkedBlockingDeque <PlaybackStateCompat > playbackStateCompats ,
1526
+ Predicate <PlaybackStateCompat > predicate )
1527
+ throws InterruptedException {
1528
+ while (true ) {
1529
+ @ Nullable
1530
+ PlaybackStateCompat playbackStateCompat = playbackStateCompats .poll (TIMEOUT_MS , MILLISECONDS );
1531
+ if (playbackStateCompat == null ) {
1532
+ return false ;
1533
+ } else if (predicate .test (playbackStateCompat )) {
1534
+ return true ;
1535
+ }
1536
+ }
1537
+ }
1538
+
1350
1539
/**
1351
1540
* Returns an {@link Player} where {@code availableCommands} are always included and {@code
1352
1541
* excludedCommands} are always excluded from the {@linkplain Player#getAvailableCommands()
@@ -1371,4 +1560,29 @@ public boolean isCommandAvailable(int command) {
1371
1560
}
1372
1561
};
1373
1562
}
1563
+
1564
+ private static class ControllingCommandsPlayer extends SimpleBasePlayer {
1565
+
1566
+ private Commands availableCommands ;
1567
+
1568
+ public ControllingCommandsPlayer (Commands availableCommands , Looper applicationLooper ) {
1569
+ super (applicationLooper );
1570
+ this .availableCommands = availableCommands ;
1571
+ }
1572
+
1573
+ public void setAvailableCommands (Commands availableCommands ) {
1574
+ this .availableCommands = availableCommands ;
1575
+ invalidateState ();
1576
+ }
1577
+
1578
+ @ Override
1579
+ protected State getState () {
1580
+ return new State .Builder ().setAvailableCommands (availableCommands ).build ();
1581
+ }
1582
+
1583
+ @ Override
1584
+ protected ListenableFuture > handleRelease () {
1585
+ return Futures .immediateVoidFuture ();
1586
+ }
1587
+ }
1374
1588
}
0 commit comments